import { assert } from "../../../core/assert.js";

export class DataScope {
    constructor() {

        /**
         * @private
         * @type {Object[]}
         */
        this.stack = [];


        this.proxy = new Proxy(this, {
            /**
             *
             * @param target
             * @param {string} p
             * @param receiver
             * @returns {*}
             */
            get(target, p, receiver) {
                return target.read(p);
            },
            ownKeys(target) {
                const size = target.size();

                /**
                 *
                 * @type {string[]}
                 */
                const result = [];

                for (let i = 0; i < size; i++) {
                    const frame = target.stack[i];

                    const keys = Reflect.ownKeys(frame);

                    const n = keys.length;
                    for (let j = 0; j < n; j++) {
                        const key = keys[j];

                        if (!result.includes(key)) {
                            result.push(key);
                        }
                    }
                }

                return result;
            }
        });
    }

    /**
     *
     * @return {number}
     */
    size() {
        return this.stack.length;
    }

    /**
     *
     * @param {string} variable_name
     * @returns {*|undefined}
     */
    read(variable_name) {
        assert.isString(variable_name, 'variable_name');

        const stack = this.stack;

        const last = stack.length - 1;

        for (let i = last; i >= 0; i--) {
            const scope = stack[i];

            const value = scope[variable_name];

            if (value !== undefined) {
                return value;
            }
        }

        //not found
        return undefined;
    }

    /**
     *
     * @param {Object} scope
     */
    push(scope) {
        assert.defined(scope, 'scope');
        assert.notNull(scope, 'scope');

        assert.isObject(scope, 'scope');

        this.stack.push(scope);
    }

    /**
     *
     * @returns {Object}
     */
    pop() {
        assert.ok(this.stack.length > 0, 'Stack is empty');

        return this.stack.pop();
    }

    /**
     * Drop multiple frames from the top of the stack until stack size matches given value
     * @param {number} size
     * @returns {number} how many frames were dropped
     */
    unwind(size) {
        const top = this.size();

        const count = top - size;

        for (let i = 0; i < count; i++) {
            this.pop();
        }

        return count;
    }
}
