Source: lib/core/reactive/proxyHandler.js

/**
 * Factory for creating proxy handlers used in reactive state.
 * Handles normal property access and computed property redirection.
 */

export const RAW_SYMBOL = Symbol('raw');

const mutatingArrayMethods = new Set([
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse',
    'copyWithin',
    'fill'
]);

/**
 * Checks if the value is a candidate for reactive wrapping.
 * We restrict this to plain objects and arrays to avoid issues with
 * built-in classes (Date, RegExp, Map, Set, Promise) and custom class
 * instances that may contain private fields or internal slots.
 * 
 * @param {any} value - The value to check.
 * @returns {boolean} True if the value should be reactive, false otherwise.
 */
export function isReactiveTarget(value) {
    if (value === null || typeof value !== 'object') {
        return false;
    }
    const proto = Object.getPrototypeOf(value);
    return proto === null || proto === Object.prototype || proto === Array.prototype;
}

// Global context for active computed evaluations
let activeComputedKey = null;
const evaluationStack = [];

// WeakMap tracking raw target to Map of keys to Set of activeComputedKeys depending on them
const depMap = new WeakMap();

// WeakMap tracking child target to { parentTarget, parentKey } relationship
const parentMap = new WeakMap();

function track(target, key) {
    if (activeComputedKey) {
        let keysMap = depMap.get(target);
        if (!keysMap) {
            keysMap = new Map();
            depMap.set(target, keysMap);
        }
        let computedKeys = keysMap.get(key);
        if (!computedKeys) {
            computedKeys = new Set();
            keysMap.set(key, computedKeys);
        }
        computedKeys.add(activeComputedKey);
    }
}

function trigger(target, key, computedDirty) {
    // 1. Trigger dependencies on the exact key
    const keysMap = depMap.get(target);
    if (keysMap) {
        const computedKeys = keysMap.get(key);
        if (computedKeys) {
            for (const computedKey of computedKeys) {
                if (!computedDirty.get(computedKey)) {
                    computedDirty.set(computedKey, true);
                    // Propagate to other computed properties depending on this one
                    trigger(target, computedKey, computedDirty);
                }
            }
        }
    }

    // 2. Propagate invalidation to parents in case target is a nested object
    const parentRelation = parentMap.get(target);
    if (parentRelation) {
        const { parentTarget, parentKey } = parentRelation;
        trigger(parentTarget, parentKey, computedDirty);
    }
}

export class ProxyHandlerFactory {
    /**
     * @param {Object} [options={}] - Configuration options.
     * @param {string[]} [options.computedKeys=[]] - List of keys that should be treated as computed properties.
     * @param {function(): void} [options.onChange=() => {}] - Callback triggered when a property is set.
     * @param {function(string, Object): any} [options.getComputedValue=() => undefined] - Function to evaluate a computed property.
     */
    constructor({
        computedKeys = [],
        onChange = () => {},
        getComputedValue = () => undefined
    } = {}) {
        /** @type {Set<string>} @private */
        this.computedKeys = new Set(computedKeys);
        /** @type {function(): void} @private */
        this.onChange = onChange;
        /** @type {function(string, Object): any} @private */
        this.getComputedValueReal = getComputedValue;
        /** @type {WeakMap<Object, Proxy>} @private */
        this.proxyCache = new WeakMap();

        // Computed property caching maps
        /** @type {Map<string, any>} @private */
        this.computedCache = new Map();
        /** @type {Map<string, boolean>} @private */
        this.computedDirty = new Map();

        // Mark all computed properties initially dirty
        for (const key of computedKeys) {
            this.computedDirty.set(key, true);
        }
    }

    /**
     * Internal helper to evaluate and cache a computed property.
     * @param {string} key
     * @param {Object} target
     * @returns {any}
     * @private
     */
    evaluateComputedCached(key, receiver) {
        const target = receiver[RAW_SYMBOL] || receiver;
        // Track the computed property itself as a dependency of whatever outer computed is evaluating
        track(target, key);

        if (this.computedCache.has(key) && !this.computedDirty.get(key)) {
            return this.computedCache.get(key);
        }

        // Push to active evaluation context stack
        evaluationStack.push(activeComputedKey);
        activeComputedKey = key;

        try {
            const val = this.getComputedValueReal(key, receiver);
            this.computedCache.set(key, val);
            this.computedDirty.set(key, false);
            return val;
        } finally {
            activeComputedKey = evaluationStack.pop();
        }
    }

    /**
     * Creates the proxy handler object.
     * @returns {ProxyHandler<Object>}
     */
    create() {
        return {
            set: (target, key, value) => this.set(target, key, value),
            get: (target, key, receiver) => this.get(target, key, receiver),
            ownKeys: target => this.ownKeys(target),
            getOwnPropertyDescriptor: (target, key) => this.getOwnPropertyDescriptor(target, key),
            deleteProperty: (target, key) => this.deleteProperty(target, key)
        };
    }

    /**
     * Proxy 'set' trap.
     * @param {Object} target - The target object.
     * @param {string|symbol} key - The property key.
     * @param {any} value - The new value.
     * @returns {boolean}
     */
    set(target, key, value) {
        if (value && value[RAW_SYMBOL]) {
            value = value[RAW_SYMBOL];
        }
        const oldValue = target[key];
        target[key] = value;

        if (isReactiveTarget(value)) {
            parentMap.set(value, { parentTarget: target, parentKey: key });
        }

        if (oldValue !== value || (Array.isArray(target) && key === 'length')) {
            trigger(target, key, this.computedDirty);
            this.onChange();
        }
        return true;
    }

    /**
     * Proxy 'get' trap.
     * Redirects to getComputedValue if the key is a computed property.
     * @param {Object} target - The target object.
     * @param {string|symbol} key - The property key.
     * @param {Object} receiver - The proxy or object inheriting from the proxy.
     * @returns {any}
     */
    get(target, key, receiver) {
        if (key === RAW_SYMBOL) {
            return target;
        }
        if (this.computedKeys.has(key)) {
            return this.evaluateComputedCached(key, receiver);
        }
        
        // Track property access
        if (typeof key !== 'symbol') {
            track(target, key);
        }

        const value = Reflect.get(target, key, receiver);
        if (typeof value === 'function') {
            if (Array.isArray(target) && mutatingArrayMethods.has(key)) {
                return (...args) => {
                    const result = target[key](...args);
                    trigger(target, 'length', this.computedDirty);
                    this.onChange();
                    return result;
                };
            }
            return value.bind(receiver);
        }
        if (isReactiveTarget(value)) {
            parentMap.set(value, { parentTarget: target, parentKey: key });
            return this.getOrCreateProxy(value);
        }
        return value;
    }

    /**
     * Proxy 'deleteProperty' trap.
     * @param {Object} target - The target object.
     * @param {string|symbol} key - The property key.
     * @returns {boolean}
     */
    deleteProperty(target, key) {
        const hasKey = Reflect.has(target, key);
        const result = Reflect.deleteProperty(target, key);
        if (hasKey) {
            trigger(target, key, this.computedDirty);
            this.onChange();
        }
        return result;
    }

    /**
     * Proxy 'ownKeys' trap.
     * Includes computed keys in the list of keys.
     * @param {Object} target - The target object.
     * @returns {Array<string|symbol>}
     */
    ownKeys(target) {
        return [...Reflect.ownKeys(target), ...this.computedKeys];
    }

    /**
     * Proxy 'getOwnPropertyDescriptor' trap.
     * Ensures computed properties appear as own properties.
     * @param {Object} target - The target object.
     * @param {string|symbol} key - The property key.
     * @returns {PropertyDescriptor|undefined}
     */
    getOwnPropertyDescriptor(target, key) {
        if (this.computedKeys.has(key)) {
            return { enumerable: true, configurable: true };
        }
        return Reflect.getOwnPropertyDescriptor(target, key);
    }

    /**
     * Returns a cached proxy or creates a new proxy for a nested object/array.
     * @param {Object|Array} val - The nested object or array.
     * @returns {Proxy} The reactive proxy.
     * @private
     */
    getOrCreateProxy(val) {
        if (this.proxyCache.has(val)) {
            return this.proxyCache.get(val);
        }
        const handler = {
            get: (target, key, receiver) => {
                if (key === RAW_SYMBOL) {
                    return target;
                }
                if (typeof key !== 'symbol') {
                    track(target, key);
                }
                const value = Reflect.get(target, key, receiver);
                if (typeof value === 'function') {
                    if (Array.isArray(target) && mutatingArrayMethods.has(key)) {
                        return (...args) => {
                            const result = target[key](...args);
                            trigger(target, 'length', this.computedDirty);
                            this.onChange();
                            return result;
                        };
                    }
                    return value.bind(receiver);
                }
                if (isReactiveTarget(value)) {
                    parentMap.set(value, { parentTarget: target, parentKey: key });
                    return this.getOrCreateProxy(value);
                }
                return value;
            },
            set: (target, key, value) => {
                if (value && value[RAW_SYMBOL]) {
                    value = value[RAW_SYMBOL];
                }
                const oldValue = target[key];
                target[key] = value;

                if (isReactiveTarget(value)) {
                    parentMap.set(value, { parentTarget: target, parentKey: key });
                }

                if (oldValue !== value || (Array.isArray(target) && key === 'length')) {
                    trigger(target, key, this.computedDirty);
                    this.onChange();
                }
                return true;
            },
            deleteProperty: (target, key) => {
                const hasKey = Reflect.has(target, key);
                const result = Reflect.deleteProperty(target, key);
                if (hasKey) {
                    trigger(target, key, this.computedDirty);
                    this.onChange();
                }
                return result;
            }
        };
        const proxy = new Proxy(val, handler);
        this.proxyCache.set(val, proxy);
        return proxy;
    }
}