"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Functional = void 0;
const Primitive_1 = require("../../Primitive");
class Functional extends Primitive_1.Primitive {
    constructor({ i, o } = {}, opt = {}, system, id) {
        super({ i, o }, opt, system, id);
        this._looping = false;
        this._on_data_output_drop = (name) => {
            this._backward_if_ready();
            this._forward_if_ready();
        };
        this._fail = (err) => {
            this._forwarding = true;
            this.err(err);
            this._forwarding = false;
            this._forward_all_empty();
            this._backward_if_ready();
        };
        this._done = (data, err = null) => {
            if (data !== undefined) {
                this._forwarding = true;
                for (const name in this._output) {
                    const output = this._output[name];
                    const d = data[name];
                    if (d === undefined) {
                        output.pull();
                    }
                    else {
                        output.push(d);
                    }
                }
                this._forwarding = false;
            }
            this._backward_if_ready();
        };
        this.addListener('take_err', () => {
            if (this._looping) {
                this._backward_if_ready();
            }
        });
        this.addListener('take_caught_err', () => {
            if (!this.hasErr()) {
                this._backward_if_ready();
            }
        });
        this.addListener('reset', this._reset);
        this.addListener('destroy', this.d);
    }
    _reset() {
        this._looping = false;
        this.d();
    }
    _on_input_data(name) {
        if (this._i[name] !== undefined && this._i_active.size === this._i_count) {
            this._looping = false;
        }
        this._forward_if_ready();
    }
    _on_input_drop() {
        if (this._i_active.size === this._i_count - 1) {
            if (!this._backwarding && !this._forwarding_empty) {
                this._looping = false;
                if (this._catchErr) {
                    this.takeCaughtErr();
                }
                else {
                    this.takeErr();
                }
                this.d();
                this._forward_all_empty();
            }
        }
    }
    _on_input_invalid(name) {
        if (this._i_invalid.size === 1) {
            this.d();
            this._invalidate();
        }
    }
    onDataInputData(name) {
        this._on_input_data(name);
    }
    onDataInputDrop(name, data) {
        this._on_input_drop();
    }
    onDataInputStart(name) {
        if (this._i_start.size === this._i_count) {
            this._start();
        }
    }
    onDataInputInvalid(name) {
        this._on_input_invalid(name);
    }
    onDataInputEnd(name) {
        if (this._i_start.size === this._i_count - 1) {
            this._end();
        }
    }
    onRefInputData(name) {
        this._on_input_data(name);
    }
    onRefInputDrop(name, data) {
        this._on_input_drop();
    }
    onRefInputInvalid(name) {
        this._on_input_invalid(name);
    }
    onDataOutputDrop(name) {
        this._on_data_output_drop(name);
    }
    onInputSet(name, input, opt, propagate) {
        super.onInputSet(name, input, opt, propagate);
        this._invalidate();
    }
    f(i, done, fail) { }
    d() { }
    b() { }
    _backward_if_ready() {
        const { api: { window: { setTimeout }, }, } = this.__system;
        if (!this._forwarding_empty &&
            !this._forwarding &&
            !this._err &&
            !this._caughtErr &&
            this._i_count > 0 &&
            this._o_active.size === 0) {
            this._looping = false;
            this._backward_all();
            this.d();
            this.b();
            // without setTimeout an infinite loop will turn into a stack overflow
            // setTimeout(() => {
            this._forward_if_ready();
            // }, 0)
            // this.__system.tick(this._forward_if_ready.bind(this))
        }
    }
    _forward_if_ready() {
        // console.log('Functional', '_forward_if_ready')
        while (this._o_active.size - this._o_invalid.size === 0 &&
            this._i_active.size - this._i_invalid.size === this._i_count &&
            !this._forwarding &&
            !this._backwarding &&
            !this._looping // prevent async infinite while loop
        ) {
            this._looping = true;
            if (this._err !== null) {
                this._forwarding_empty = true;
                this.takeErr();
                this._forwarding_empty = false;
            }
            this._signal = Symbol();
            const signal = this._signal;
            this.f(this._i, (...args) => {
                if (signal !== this._signal) {
                    return;
                }
                this._done(...args);
            }, this._fail);
        }
    }
    snapshotSelf() {
        return {
            ...super.snapshotSelf(),
            ...(this._looping ? { _looping: this._looping } : {}),
        };
    }
    restoreSelf(state) {
        const { _looping, ...rest } = state;
        super.restoreSelf(rest);
        this._looping = _looping;
    }
}
exports.Functional = Functional;
//# sourceMappingURL=index.js.map