"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var g = generator.apply(thisArg, _arguments || []), i, q = [];
    return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
    function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
    function fulfill(value) { resume("next", value); }
    function reject(value) { resume("throw", value); }
    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.join = exports.reduce = exports.timer = exports.throttle = exports.pipe = exports.filter = exports.tryMap = exports.map = exports.mutable = exports.EventualValue = void 0;
const util_1 = require("../util");
class EventualValue {
    constructor(initial) {
        this.subscribers = [];
        this.inner = initial;
        this.promise = new Promise(resolve => {
            if (this.inner !== undefined) {
                resolve(this.inner);
            }
            else {
                this.resolvePromise = resolve;
            }
        });
    }
    get valueReady() {
        return this.inner !== undefined;
    }
    value() {
        if (this.promise) {
            return this.promise;
        }
        else {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            return Promise.resolve(this.inner);
        }
    }
    subscribe(subscriber) {
        this.subscribers.push(subscriber);
        if (this.inner !== undefined) {
            subscriber(this.inner);
        }
    }
    push(value) {
        var _a;
        if (!util_1.equal(this.inner, value)) {
            this.inner = value;
            (_a = this.resolvePromise) === null || _a === void 0 ? void 0 : _a.call(this, value);
            this.promise = undefined;
            this.resolvePromise = undefined;
            this.subscribers.forEach(subscriber => subscriber(value));
        }
    }
    map(f) {
        return map(this, f);
    }
    tryMap(f, options) {
        return tryMap(this, f, options);
    }
    filter(f) {
        return filter(this, f);
    }
    pipe(f) {
        return pipe(this, f);
    }
    throttle(interval) {
        return throttle(this, interval);
    }
    reduce(f, initial) {
        return reduce(this, f, initial);
    }
    values() {
        return __asyncGenerator(this, arguments, function* values_1() {
            // Creates a promise and exposes its `resolve` method so it can be triggered
            // externally
            function defer() {
                let resolve = null;
                const promise = new Promise(_resolve => {
                    resolve = _resolve;
                });
                const deferred = {
                    promise,
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    resolve: resolve,
                };
                return deferred;
            }
            // Create the initial promise
            let next = defer();
            // Delay this ever so slightly to allow `await next.promise` to be executed
            // before we resolve the first value. Otherwise we'd skip the value at the
            // time `values()` is called, because `await next.promise` would await the
            // second, not the initial promise.
            setTimeout(() => 
            // Whenever there is a new value, resolve the current promise
            // and replace it with a new one
            this.pipe(t => {
                next.resolve(t);
                next = defer();
            }), 0);
            while (true) {
                yield yield __await(yield __await(next.promise));
            }
        });
    }
}
exports.EventualValue = EventualValue;
function mutable(initial) {
    return new EventualValue(initial);
}
exports.mutable = mutable;
function map(source, mapper) {
    const output = mutable();
    let previousT;
    let latestT;
    let mapPromise;
    source.subscribe(t => {
        latestT = t;
        if (mapPromise === undefined) {
            mapPromise = (() => __awaiter(this, void 0, void 0, function* () {
                while (!util_1.equal(latestT, previousT)) {
                    previousT = latestT;
                    output.push(yield mapper(latestT));
                }
                mapPromise = undefined;
            }))();
        }
    });
    return output;
}
exports.map = map;
function tryMap(source, mapper, { onError }) {
    const output = mutable();
    let previousT;
    let latestT;
    let promiseActive = false;
    source.subscribe(t => {
        latestT = t;
        if (!promiseActive) {
            promiseActive = true;
            (() => __awaiter(this, void 0, void 0, function* () {
                while (!util_1.equal(latestT, previousT)) {
                    try {
                        previousT = latestT;
                        output.push(yield mapper(latestT));
                    }
                    catch (err) {
                        onError(err);
                    }
                }
                promiseActive = false;
            }))();
        }
    });
    return output;
}
exports.tryMap = tryMap;
function filter(source, f) {
    const output = mutable();
    let previousT;
    let latestT;
    let mapPromise;
    source.subscribe(t => {
        latestT = t;
        if (mapPromise === undefined) {
            mapPromise = (() => __awaiter(this, void 0, void 0, function* () {
                while (!util_1.equal(latestT, previousT)) {
                    previousT = latestT;
                    if (yield f(latestT)) {
                        output.push(latestT);
                    }
                }
                mapPromise = undefined;
            }))();
        }
    });
    return output;
}
exports.filter = filter;
function pipe(source, fn) {
    map(source, fn);
}
exports.pipe = pipe;
function throttle(source, interval) {
    const output = mutable();
    let latestT;
    let timeout;
    let lastPushed = Date.now();
    source.subscribe(t => {
        if (!output.valueReady) {
            latestT = t;
            output.push(t);
            lastPushed = Date.now();
        }
        else if (!util_1.equal(t, latestT)) {
            latestT = t;
            if (!timeout) {
                timeout = setTimeout(() => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    output.push(latestT);
                    lastPushed = Date.now();
                    timeout = undefined;
                }, Math.max(0, Math.min(interval, Date.now() - lastPushed)));
            }
        }
    });
    return output;
}
exports.throttle = throttle;
function timer(milliseconds) {
    const time = mutable(Date.now());
    setInterval(() => time.push(Date.now()), milliseconds);
    return time;
}
exports.timer = timer;
function reduce(source, reducer, initial) {
    const output = mutable(initial);
    let acc = initial;
    let previousT;
    let latestT;
    let promiseActive = false;
    source.subscribe(t => {
        latestT = t;
        if (!promiseActive) {
            promiseActive = true;
            (() => __awaiter(this, void 0, void 0, function* () {
                while (!util_1.equal(latestT, previousT)) {
                    previousT = latestT;
                    acc = yield reducer(acc, latestT);
                    output.push(acc);
                }
                promiseActive = false;
            }))();
        }
    });
    return output;
}
exports.reduce = reduce;
function join(sources) {
    const output = mutable();
    const keys = Object.keys(sources);
    const sourceValues = keys.reduce((out, key) => {
        out[key] = undefined;
        return out;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
    }, {});
    for (const key of keys) {
        sources[key].subscribe(value => {
            sourceValues[key] = value;
            if (!keys.some(key => sourceValues[key] === undefined)) {
                // NOTE: creating a new JS object is important, otherwise
                // `output.inner` and `sourceValues` will be the same object
                // and therefore always be considered identical
                output.push(Object.assign({}, sourceValues));
            }
        });
    }
    return output;
}
exports.join = join;
//# sourceMappingURL=eventual.js.map