"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.unfailable = exports.promise = exports.tryCatch = exports.succeedWith = exports.suspend = exports.zip_ = exports.zip = exports.zipWith_ = exports.zipWith = exports.orElseEither_ = exports.orElseEither = exports.either = exports.environment = exports.access = exports.accessM = exports.provide = exports.provideAll_ = exports.provideAll = exports.provideSome = exports.unit = exports.mapError_ = exports.mapError = exports.bimap_ = exports.bimap = exports.catchAll_ = exports.catchAll = exports.fold_ = exports.fold = exports.foldM = exports.foldM_ = exports.map = exports.map_ = exports.fail = exports.succeed = exports.tap_ = exports.tap = exports.chain_ = exports.chain = exports.runAsyncEnv = exports.runAsync = exports.runPromise = exports.runPromiseExit = exports.runPromiseExitEnv = exports.tracingContext = exports.Tracer = exports.successExit = exports.interruptExit = exports.failExit = exports.InterruptionState = exports.Async = void 0;
exports.tryCatchOption = exports.union = exports.unionFn = exports.fromEither = exports.let = exports.bind = exports.delay = exports.sleep = exports.tapError = exports.done = void 0;
// ets_tracing: off
/* eslint-disable prefer-const */
const Tp = __importStar(require("../Collections/Immutable/Tuple"));
const commons_1 = require("../Effect/commons");
const E = __importStar(require("../Either"));
const Function_1 = require("../Function");
const Stack_1 = require("../Stack");
class Async {
}
exports.Async = Async;
/**
 * @ets_optimize identity
 */
function concrete(_) {
    return _;
}
class ISucceed extends Async {
    constructor(a) {
        super();
        this.a = a;
        this._asyncTag = "Succeed";
    }
}
class ISuspend extends Async {
    constructor(f) {
        super();
        this.f = f;
        this._asyncTag = "Suspend";
    }
}
class IFail extends Async {
    constructor(e) {
        super();
        this.e = e;
        this._asyncTag = "Fail";
    }
}
class IFlatMap extends Async {
    constructor(value, cont) {
        super();
        this.value = value;
        this.cont = cont;
        this._asyncTag = "FlatMap";
    }
}
class IFold extends Async {
    constructor(value, failure, success) {
        super();
        this.value = value;
        this.failure = failure;
        this.success = success;
        this._asyncTag = "Fold";
    }
}
class IAccess extends Async {
    constructor(access) {
        super();
        this.access = access;
        this._asyncTag = "Access";
    }
}
class IProvide extends Async {
    constructor(r, cont) {
        super();
        this.r = r;
        this.cont = cont;
        this._asyncTag = "Provide";
    }
}
class IPromise extends Async {
    constructor(promise, onError) {
        super();
        this.promise = promise;
        this.onError = onError;
        this._asyncTag = "Promise";
    }
}
class IDone extends Async {
    constructor(exit) {
        super();
        this.exit = exit;
        this._asyncTag = "Done";
    }
}
class FoldFrame {
    constructor(failure, apply) {
        this.failure = failure;
        this.apply = apply;
        this._asyncTag = "FoldFrame";
    }
}
class ApplyFrame {
    constructor(apply) {
        this.apply = apply;
        this._asyncTag = "ApplyFrame";
    }
}
/**
 * Models the state of interruption, allows for listening to interruption events & firing interruption events
 */
class InterruptionState {
    constructor() {
        this.isInterrupted = false;
        this.listeners = new Set();
    }
    // listen to an interruption event
    listen(f) {
        this.listeners.add(f);
        return () => {
            // stop listening
            this.listeners.delete(f);
        };
    }
    get interrupted() {
        return this.isInterrupted;
    }
    interrupt() {
        if (!this.isInterrupted) {
            // set to interrupted
            this.isInterrupted = true;
            // notify
            this.listeners.forEach((i) => {
                i();
            });
        }
    }
}
exports.InterruptionState = InterruptionState;
const failExit = (e) => ({
    _tag: "Failure",
    e
});
exports.failExit = failExit;
exports.interruptExit = {
    _tag: "Interrupt"
};
const successExit = (a) => ({
    _tag: "Success",
    a
});
exports.successExit = successExit;
/**
 * Models a cancellable promise
 */
class CancelablePromise {
    constructor(
    // creates the promise
    promiseFactory, 
    // listens for interruption events
    is) {
        this.promiseFactory = promiseFactory;
        this.is = is;
        // gets called with a Rejection<E>, any here is to not break covariance imposed by _E
        this.rejection = undefined;
        // holds the current running promise
        this.current = undefined;
        // creates the computation linking it to the interruption state
        this.promise = () => {
            if (this.current) {
                throw new Error("Bug: promise() have been called twice");
            }
            else if (this.is.interrupted) {
                throw new Error("Bug: trying to create a promise already interrupted");
            }
            else {
                const onInterrupt = [];
                // we record the current interrupt in the interruption registry
                const removeListener = this.is.listen(() => {
                    onInterrupt.forEach((f) => {
                        f();
                    });
                    this.interrupt();
                });
                const p = new Promise((res, rej) => {
                    // set the rejection handler
                    this.rejection = rej;
                    // creates the underlying promise
                    this.promiseFactory((f) => {
                        onInterrupt.push(f);
                    })
                        .then((a) => {
                        // removes the call to interrupt from the interruption registry
                        removeListener();
                        // if not interrupted we continue
                        if (!this.is.interrupted) {
                            res(a);
                        }
                    })
                        .catch((e) => {
                        // removes the call to interrupt from the interruption registry
                        removeListener();
                        // if not interrupted we continue
                        if (!this.is.interrupted) {
                            rej(e);
                        }
                    });
                });
                // track the current running promise to avoid re-creation
                this.current = p;
                // return the promise
                return p;
            }
        };
        this.interrupt = () => {
            var _a;
            // triggeres a promise rejection on the current promise with an interrupt exit
            (_a = this.rejection) === null || _a === void 0 ? void 0 : _a.call(this, exports.interruptExit);
        };
    }
}
class Tracer {
    constructor() {
        this.running = new Set();
        this.traced = this.traced.bind(this);
        this.wait = this.wait.bind(this);
        this.clear = this.clear.bind(this);
    }
    // tracks a lazy promise lifetime
    traced(promise) {
        return async () => {
            const p = promise();
            this.running.add(p);
            try {
                const a = await p;
                this.running.delete(p);
                return Promise.resolve(a);
            }
            catch (e) {
                this.running.delete(p);
                return Promise.reject(e);
            }
        };
    }
    // awaits for all the running promises to complete
    async wait() {
        const t = await Promise.all(Array.from(this.running).map((p) => p.then((a) => (0, exports.successExit)(a)).catch((e) => Promise.resolve(e))));
        return await new Promise((r) => {
            setTimeout(() => {
                r(t);
            }, 0);
        });
    }
    // clears itself
    clear() {
        this.running.clear();
    }
}
exports.Tracer = Tracer;
// create the root tracing context
exports.tracingContext = new Tracer();
/**
 * Runs this computation with the specified initial state, returning either a
 * failure or the updated state and the result
 */
function runPromiseExitEnv(self, ri, is = new InterruptionState()) {
    return exports.tracingContext.traced(async () => {
        let stack = undefined;
        let a = null;
        let r = ri;
        let failed = false;
        let curAsync = self;
        let cnt = 0;
        let interruptedLocal = false;
        function isInterruted() {
            return interruptedLocal || is.interrupted;
        }
        function pop() {
            const nextInstr = stack;
            if (nextInstr) {
                stack = stack === null || stack === void 0 ? void 0 : stack.previous;
            }
            return nextInstr === null || nextInstr === void 0 ? void 0 : nextInstr.value;
        }
        function push(cont) {
            stack = new Stack_1.Stack(cont, stack);
        }
        function findNextErrorHandler() {
            let unwinding = true;
            while (unwinding) {
                const nextInstr = pop();
                if (nextInstr == null) {
                    unwinding = false;
                }
                else {
                    if (nextInstr._asyncTag === "FoldFrame") {
                        unwinding = false;
                        push(new ApplyFrame(nextInstr.failure));
                    }
                }
            }
        }
        while (curAsync != null && !isInterruted()) {
            if (cnt > 10000) {
                await new Promise((r) => {
                    setTimeout(() => {
                        r(undefined);
                    }, 0);
                });
                cnt = 0;
            }
            cnt += 1;
            const xp = curAsync;
            switch (xp._asyncTag) {
                case "FlatMap": {
                    const nested = xp.value;
                    const continuation = xp.cont;
                    switch (nested._asyncTag) {
                        case "Succeed": {
                            curAsync = continuation(nested.a);
                            break;
                        }
                        default: {
                            curAsync = nested;
                            push(new ApplyFrame(continuation));
                        }
                    }
                    break;
                }
                case "Suspend": {
                    curAsync = xp.f();
                    break;
                }
                case "Succeed": {
                    a = xp.a;
                    const nextInstr = pop();
                    if (nextInstr) {
                        curAsync = nextInstr.apply(a);
                    }
                    else {
                        curAsync = undefined;
                    }
                    break;
                }
                case "Fail": {
                    findNextErrorHandler();
                    const nextInst = pop();
                    if (nextInst) {
                        curAsync = nextInst.apply(xp.e);
                    }
                    else {
                        failed = true;
                        a = xp.e;
                        curAsync = undefined;
                    }
                    break;
                }
                case "Fold": {
                    curAsync = xp.value;
                    push(new FoldFrame(xp.failure, xp.success));
                    break;
                }
                case "Done": {
                    switch (xp.exit._tag) {
                        case "Failure": {
                            curAsync = new IFail(xp.exit.e);
                            break;
                        }
                        case "Interrupt": {
                            interruptedLocal = true;
                            curAsync = undefined;
                            break;
                        }
                        case "Success": {
                            curAsync = new ISucceed(xp.exit.a);
                            break;
                        }
                    }
                    break;
                }
                case "Access": {
                    curAsync = xp.access(r);
                    break;
                }
                case "Provide": {
                    r = xp.r;
                    curAsync = xp.cont;
                    break;
                }
                case "Promise": {
                    try {
                        curAsync = new ISucceed(await new CancelablePromise((s) => xp.promise(s).catch((e) => Promise.reject((0, exports.failExit)(xp.onError(e)))), is).promise());
                    }
                    catch (e) {
                        const e_ = e;
                        switch (e_._tag) {
                            case "Failure": {
                                curAsync = new IFail(e_.e);
                                break;
                            }
                            case "Interrupt": {
                                interruptedLocal = true;
                                curAsync = undefined;
                                break;
                            }
                        }
                    }
                    break;
                }
            }
        }
        if (is.interrupted) {
            return exports.interruptExit;
        }
        if (failed) {
            return (0, exports.failExit)(a);
        }
        return (0, exports.successExit)(a);
    })();
}
exports.runPromiseExitEnv = runPromiseExitEnv;
function runPromiseExit(self, is = new InterruptionState()) {
    return runPromiseExitEnv(self, {}, is);
}
exports.runPromiseExit = runPromiseExit;
// runs as a Promise of an Exit
async function runPromise(task, is = new InterruptionState()) {
    return runPromiseExit(task, is).then((e) => e._tag === "Failure"
        ? Promise.reject(e.e)
        : e._tag === "Interrupt"
            ? Promise.reject(e)
            : Promise.resolve(e.a));
}
exports.runPromise = runPromise;
// runs as a Cancellable
function runAsync(task, cb) {
    const is = new InterruptionState();
    runPromiseExit(task, is).then(cb);
    return () => {
        is.interrupt();
    };
}
exports.runAsync = runAsync;
// runs as a Cancellable
function runAsyncEnv(task, r, cb) {
    const is = new InterruptionState();
    runPromiseExitEnv(task, r, is).then(cb);
    return () => {
        is.interrupt();
    };
}
exports.runAsyncEnv = runAsyncEnv;
/**
 * Extends this computation with another computation that depends on the
 * result of this computation by running the first computation, using its
 * result to generate a second computation, and running that computation.
 */
function chain(f) {
    return (self) => new IFlatMap(self, f);
}
exports.chain = chain;
/**
 * Extends this computation with another computation that depends on the
 * result of this computation by running the first computation, using its
 * result to generate a second computation, and running that computation.
 */
function chain_(self, f) {
    return new IFlatMap(self, f);
}
exports.chain_ = chain_;
/**
 * Returns a computation that effectfully "peeks" at the success of this one.
 */
function tap(f) {
    return (self) => tap_(self, f);
}
exports.tap = tap;
/**
 * Returns a computation that effectfully "peeks" at the success of this one.
 */
function tap_(self, f) {
    return chain_(self, (a) => map_(f(a), () => a));
}
exports.tap_ = tap_;
/**
 * Constructs a computation that always succeeds with the specified value.
 */
function succeed(a) {
    return new ISucceed(a);
}
exports.succeed = succeed;
/**
 * Constructs a computation that always succeeds with the specified value,
 * passing the state through unchanged.
 */
function fail(a) {
    return new IFail(a);
}
exports.fail = fail;
/**
 * Extends this computation with another computation that depends on the
 * result of this computation by running the first computation, using its
 * result to generate a second computation, and running that computation.
 */
function map_(self, f) {
    return chain_(self, (a) => succeed(f(a)));
}
exports.map_ = map_;
/**
 * Extends this computation with another computation that depends on the
 * result of this computation by running the first computation, using its
 * result to generate a second computation, and running that computation.
 */
function map(f) {
    return (self) => map_(self, f);
}
exports.map = map;
/**
 * Recovers from errors by accepting one computation to execute for the case
 * of an error, and one computation to execute for the case of success.
 */
function foldM_(self, failure, success) {
    return new IFold(self, failure, success);
}
exports.foldM_ = foldM_;
/**
 * Recovers from errors by accepting one computation to execute for the case
 * of an error, and one computation to execute for the case of success.
 */
function foldM(failure, success) {
    return (self) => foldM_(self, failure, success);
}
exports.foldM = foldM;
/**
 * Folds over the failed or successful results of this computation to yield
 * a computation that does not fail, but succeeds with the value of the left
 * or righr function passed to `fold`.
 */
function fold(failure, success) {
    return (self) => fold_(self, failure, success);
}
exports.fold = fold;
/**
 * Folds over the failed or successful results of this computation to yield
 * a computation that does not fail, but succeeds with the value of the left
 * or righr function passed to `fold`.
 */
function fold_(self, failure, success) {
    return foldM_(self, (e) => succeed(failure(e)), (a) => succeed(success(a)));
}
exports.fold_ = fold_;
/**
 * Recovers from all errors.
 */
function catchAll(failure) {
    return (self) => catchAll_(self, failure);
}
exports.catchAll = catchAll;
/**
 * Recovers from all errors.
 */
function catchAll_(self, failure) {
    return foldM_(self, failure, (a) => succeed(a));
}
exports.catchAll_ = catchAll_;
/**
 * Returns a computation whose error and success channels have been mapped
 * by the specified functions, `f` and `g`.
 */
function bimap(f, g) {
    return (self) => bimap_(self, f, g);
}
exports.bimap = bimap;
/**
 * Returns a computation whose error and success channels have been mapped
 * by the specified functions, `f` and `g`.
 */
function bimap_(self, f, g) {
    return foldM_(self, (e) => fail(f(e)), (a) => succeed(() => g(a)));
}
exports.bimap_ = bimap_;
/**
 * Transforms the error type of this computation with the specified
 * function.
 */
function mapError(f) {
    return (self) => mapError_(self, f);
}
exports.mapError = mapError;
/**
 * Transforms the error type of this computation with the specified
 * function.
 */
function mapError_(self, f) {
    return catchAll_(self, (e) => fail(f(e)));
}
exports.mapError_ = mapError_;
/**
 * Constructs a computation that always returns the `Unit` value, passing the
 * state through unchanged.
 */
exports.unit = succeed(undefined);
/**
 * Transforms the initial state of this computation` with the specified
 * function.
 */
function provideSome(f) {
    return (self) => accessM((r) => provideAll(f(r))(self));
}
exports.provideSome = provideSome;
/**
 * Provides this computation with its required environment.
 */
function provideAll(r) {
    return (self) => new IProvide(r, self);
}
exports.provideAll = provideAll;
/**
 * Provides this computation with its required environment.
 */
function provideAll_(self, r) {
    return new IProvide(r, self);
}
exports.provideAll_ = provideAll_;
/**
 * Provides some of the environment required to run this effect,
 * leaving the remainder `R0` and combining it automatically using spread.
 */
function provide(r) {
    return (next) => provideSome((r0) => ({ ...r0, ...r }))(next);
}
exports.provide = provide;
/**
 * Access the environment monadically
 */
function accessM(f) {
    return new IAccess(f);
}
exports.accessM = accessM;
/**
 * Access the environment with the function f
 */
function access(f) {
    return accessM((r) => succeed(f(r)));
}
exports.access = access;
/**
 * Access the environment
 */
function environment() {
    return accessM((r) => succeed(r));
}
exports.environment = environment;
/**
 * Returns a computation whose failure and success have been lifted into an
 * `Either`. The resulting computation cannot fail, because the failure case
 * has been exposed as part of the `Either` success case.
 */
function either(self) {
    return fold_(self, E.left, E.right);
}
exports.either = either;
/**
 * Executes this computation and returns its value, if it succeeds, but
 * otherwise executes the specified computation.
 */
function orElseEither(that) {
    return (self) => orElseEither_(self, that);
}
exports.orElseEither = orElseEither;
/**
 * Executes this computation and returns its value, if it succeeds, but
 * otherwise executes the specified computation.
 */
function orElseEither_(self, that) {
    return foldM_(self, () => map_(that(), (a) => E.right(a)), (a) => succeed(E.left(a)));
}
exports.orElseEither_ = orElseEither_;
/**
 * Combines this computation with the specified computation, passing the
 * updated state from this computation to that computation and combining the
 * results of both using the specified function.
 */
function zipWith(that, f) {
    return (self) => zipWith_(self, that, f);
}
exports.zipWith = zipWith;
/**
 * Combines this computation with the specified computation, passing the
 * updated state from this computation to that computation and combining the
 * results of both using the specified function.
 */
function zipWith_(self, that, f) {
    return chain_(self, (a) => map_(that, (b) => f(a, b)));
}
exports.zipWith_ = zipWith_;
/**
 * Combines this computation with the specified computation, passing the
 * updated state from this computation to that computation and combining the
 * results of both into a tuple.
 */
function zip(that) {
    return (self) => zip_(self, that);
}
exports.zip = zip;
/**
 * Combines this computation with the specified computation, passing the
 * updated state from this computation to that computation and combining the
 * results of both into a tuple.
 */
function zip_(self, that) {
    return zipWith_(self, that, Tp.tuple);
}
exports.zip_ = zip_;
/**
 * Suspend a computation, useful in recursion
 */
function suspend(f) {
    return new ISuspend(f);
}
exports.suspend = suspend;
/**
 * Lift a sync (non failable) computation
 */
function succeedWith(f) {
    return suspend(() => succeed(f()));
}
exports.succeedWith = succeedWith;
/**
 * Lift a sync (non failable) computation
 */
function tryCatch(f, onThrow) {
    return suspend(() => {
        try {
            return succeed(f());
        }
        catch (u) {
            return fail(onThrow(u));
        }
    });
}
exports.tryCatch = tryCatch;
// construct from a promise
function promise(promise, onError) {
    return new IPromise(promise, onError);
}
exports.promise = promise;
// construct from a non failable promise
function unfailable(promise) {
    return new IPromise(promise, () => undefined);
}
exports.unfailable = unfailable;
// construct a Task from an exit value
function done(exit) {
    return new IDone(exit);
}
exports.done = done;
// like .then in Promise when the result of f is a Promise but ignores the outout of f
// useful for logging or doing things that should not change the result
function tapError(f) {
    return (self) => (catchAll((e) => (chain((_) => fail(e))(f(e))))(self));
}
exports.tapError = tapError;
// sleeps for ms milliseconds
function sleep(ms) {
    return unfailable((onInterrupt) => new Promise((res) => {
        const timer = setTimeout(() => {
            res(undefined);
        }, ms);
        onInterrupt(() => {
            clearTimeout(timer);
        });
    }));
}
exports.sleep = sleep;
// delay the computation prepending a sleep of ms milliseconds
function delay(ms) {
    return (self) => (chain(() => self)(sleep(ms)));
}
exports.delay = delay;
// binds the output of a computation to a variable
// useful for imperative style, like using async/await
function bind(k) {
    return (f) => (self) => (chain((s) => (map((a1) => ({ ...s, [k]: a1 }))(f(s))))(self));
}
exports.bind = bind;
// binds the result of a function to a variable
// useful for imperative style, like using async/await
function assign(k) {
    return (f) => (self) => (map((s) => (((a1) => ({ ...s, [k]: a1 }))(f(s))))(self));
}
exports.let = assign;
// list an Either
function fromEither(e) {
    return e._tag === "Right" ? succeed(e.right) : fail(e.left);
}
exports.fromEither = fromEither;
/**
 * Compact the union produced by the result of f
 *
 * @ets_optimize identity
 */
function unionFn(_) {
    return _;
}
exports.unionFn = unionFn;
/**
 * Compact the union
 *
 * @ets_optimize identity
 */
function union(_) {
    return _;
}
exports.union = union;
/**
 * Get the A from an option
 */
function tryCatchOption_(ma, onNone) {
    return (fromEither(E.fromOption_(ma, onNone)));
}
exports.default = tryCatchOption_;
/**
 * Get the A from an option
 *
 * @ets_data_first tryCatchOption_
 */
function tryCatchOption(onNone) {
    return (ma) => tryCatchOption_(ma, onNone);
}
exports.tryCatchOption = tryCatchOption;
//# sourceMappingURL=core.js.map