import { assert } from "../../../assert.js";
import { array_push_if_unique } from "../../../collection/array/array_push_if_unique.js";
import { compileReactiveExpression } from "../../../lang/reactive/compileReactiveExpression.js";
import DataType from "../../../parser/simple/DataType.js";
import { inferReactiveExpressionTypes } from "../transform/inferReactiveExpressionTypes.js";

export class ReactiveTrigger {
    /**
     *
     * @param {string} code
     */
    constructor(code) {

        assert.isString(code, "code");

        /**
         * @readonly
         * @type {ReactiveExpression}
         */
        this.expression = compileReactiveExpression(code);

        //infer types
        inferReactiveExpressionTypes(this.expression);

        if (this.expression.dataType === DataType.Any) {
            //enforce top level type
            this.expression.dataType = DataType.Boolean;
        }

        /**
         * Cached flat list of references
         * @type {ReactiveReference[]}
         */
        this.references = [];

        this.expression.traverse(
            /**
             *
             * @param {ReactiveExpression|ReactiveReference} node
             */
            (node) => {
                if (node.isReference) {
                    array_push_if_unique(this.references, node);
                }
            });

    }

    /**
     *
     * @param {function(ReactiveReference)} visitor
     * @param {*} [thisArg]
     */
    traverseReferences(visitor, thisArg) {
        this.references.forEach(visitor, thisArg);
    }

    /**
     * Given a dictionary-style object, connect references in the expression to values of properties with matching names.
     * @example given object {a: Vector1} and expression a > 7, value of A will be taken from that input object
     * @param {Object} data
     */
    connect(data) {
        assert.isObject(data, 'data');

        const references = this.references;
        const ref_count = references.length;

        for (let i = 0; i < ref_count; i++) {
            const ref = references[i];
            const refName = ref.name;
            const datum = data[refName];

            ref.connect(datum);
        }
    }

    disconnect() {
        const references = this.references;
        const ref_count = references.length;

        for (let i = 0; i < ref_count; i++) {
            const ref = references[i];
            ref.disconnect();
        }
    }
}
