"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.runLog = exports.runEither = exports.runState = exports.runState_ = exports.run = exports.runAll = exports.runAll_ = 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.contramapInput = exports.unit = exports.update = exports.set = exports.modify = 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.logWith = exports.log = exports.succeed = exports.tap_ = exports.tap = exports.chain_ = exports.chain = exports.XPureBase = void 0;
exports.runResult = exports.runResult_ = void 0;
// ets_tracing: off
/* eslint-disable prefer-const */
const Chunk = __importStar(require("../Collections/Immutable/Chunk/core"));
const Tp = __importStar(require("../Collections/Immutable/Tuple"));
const commons_1 = require("../Effect/commons");
const E = __importStar(require("../Either/core"));
const Stack_1 = require("../Stack");
class XPureBase {
    constructor() {
        this._tag = "XPure";
    }
}
exports.XPureBase = XPureBase;
/**
 * @ets_optimize remove
 */
function concrete(_) {
    //
}
class Succeed extends XPureBase {
    constructor(a) {
        super();
        this.a = a;
        this._xptag = "Succeed";
    }
}
class Log extends XPureBase {
    constructor(w) {
        super();
        this.w = w;
        this._xptag = "Log";
    }
}
class Suspend extends XPureBase {
    constructor(f) {
        super();
        this.f = f;
        this._xptag = "Suspend";
    }
}
class Fail extends XPureBase {
    constructor(e) {
        super();
        this.e = e;
        this._xptag = "Fail";
    }
}
class Modify extends XPureBase {
    constructor(run) {
        super();
        this.run = run;
        this._xptag = "Modify";
    }
}
class FlatMap extends XPureBase {
    constructor(value, cont) {
        super();
        this.value = value;
        this.cont = cont;
        this._xptag = "FlatMap";
    }
}
class Fold extends XPureBase {
    constructor(value, failure, success) {
        super();
        this.value = value;
        this.failure = failure;
        this.success = success;
        this._xptag = "Fold";
    }
}
class Access extends XPureBase {
    constructor(access) {
        super();
        this.access = access;
        this._xptag = "Access";
    }
}
class Provide extends XPureBase {
    constructor(r, cont) {
        super();
        this.r = r;
        this.cont = cont;
        this._xptag = "Provide";
    }
}
/**
 * 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 FlatMap(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 FlatMap(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,
 * passing the state through unchanged.
 */
function succeed(a) {
    return new Succeed(a);
}
exports.succeed = succeed;
/**
 * Constructs a computation that logs w.
 */
function log(w) {
    return new Log(w);
}
exports.log = log;
/**
 * Constructs a computation that logs w.
 */
function logWith(f) {
    return suspend(() => log(f()));
}
exports.logWith = logWith;
/**
 * Constructs a computation that always succeeds with the specified value,
 * passing the state through unchanged.
 */
function fail(a) {
    return new Fail(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) => chain_(self, (a) => succeed(f(a)));
}
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 Fold(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 from the specified modify function.
 */
function modify(f) {
    return new Modify(f);
}
exports.modify = modify;
/**
 * Constructs a computation from the specified modify function.
 */
function set(s) {
    return modify(() => Tp.tuple(s, undefined));
}
exports.set = set;
/**
 * Constructs a computation from the specified update function.
 */
function update(f) {
    return modify((s) => Tp.tuple(f(s), undefined));
}
exports.update = update;
/**
 * 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 contramapInput(f) {
    return (self) => chain_(update(f), () => self);
}
exports.contramapInput = contramapInput;
/**
 * 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 Provide(r, self);
}
exports.provideAll = provideAll;
/**
 * Provides this computation with its required environment.
 */
function provideAll_(self, r) {
    return new Provide(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 Access(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 Suspend(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(onThrow) {
    return (f) => suspend(() => {
        try {
            return succeed(f());
        }
        catch (u) {
            return fail(onThrow(u));
        }
    });
}
exports.tryCatch = tryCatch;
class FoldFrame {
    constructor(failure, apply) {
        this.failure = failure;
        this.apply = apply;
        this._xptag = "FoldFrame";
    }
}
class ApplyFrame {
    constructor(apply) {
        this.apply = apply;
        this._xptag = "ApplyFrame";
    }
}
class Runtime {
    constructor() {
        this.stack = undefined;
    }
    pop() {
        var _a;
        const nextInstr = this.stack;
        if (nextInstr) {
            this.stack = (_a = this.stack) === null || _a === void 0 ? void 0 : _a.previous;
        }
        return nextInstr === null || nextInstr === void 0 ? void 0 : nextInstr.value;
    }
    push(cont) {
        this.stack = new Stack_1.Stack(cont, this.stack);
    }
    findNextErrorHandler() {
        let unwinding = true;
        while (unwinding) {
            const nextInstr = this.pop();
            if (nextInstr == null) {
                unwinding = false;
            }
            else {
                if (nextInstr._xptag === "FoldFrame") {
                    unwinding = false;
                    this.push(new ApplyFrame(nextInstr.failure));
                }
            }
        }
    }
    runAll(self, s) {
        let s0 = s;
        let a = undefined;
        let environments = undefined;
        let failed = false;
        let curXPure = self;
        let logs = Chunk.empty();
        while (curXPure != null) {
            ;
            const xp = curXPure;
            switch (xp._xptag) {
                case "FlatMap": {
                    ;
                    const nested = xp.value;
                    const continuation = xp.cont;
                    switch (nested._xptag) {
                        case "Succeed": {
                            curXPure = continuation(nested.a);
                            break;
                        }
                        case "Modify": {
                            const updated = nested.run(s0);
                            s0 = updated.get(0);
                            a = updated.get(1);
                            curXPure = continuation(a);
                            break;
                        }
                        default: {
                            curXPure = nested;
                            this.push(new ApplyFrame(continuation));
                        }
                    }
                    break;
                }
                case "Log": {
                    logs = Chunk.append_(logs, xp.w);
                    a = undefined;
                    const nextInstr = this.pop();
                    curXPure = nextInstr === null || nextInstr === void 0 ? void 0 : nextInstr.apply(a);
                    break;
                }
                case "Suspend": {
                    curXPure = xp.f();
                    break;
                }
                case "Succeed": {
                    a = xp.a;
                    const nextInstr = this.pop();
                    if (nextInstr) {
                        curXPure = nextInstr.apply(a);
                    }
                    else {
                        curXPure = undefined;
                    }
                    break;
                }
                case "Fail": {
                    this.findNextErrorHandler();
                    const nextInst = this.pop();
                    if (nextInst) {
                        curXPure = nextInst.apply(xp.e);
                    }
                    else {
                        failed = true;
                        a = xp.e;
                        curXPure = undefined;
                    }
                    break;
                }
                case "Fold": {
                    const state = s0;
                    this.push(new FoldFrame((c) => chain_(set(state), () => xp.failure(c)), xp.success));
                    curXPure = xp.value;
                    break;
                }
                case "Access": {
                    curXPure = xp.access((environments === null || environments === void 0 ? void 0 : environments.value) || {});
                    break;
                }
                case "Provide": {
                    environments = new Stack_1.Stack(xp.r, environments);
                    curXPure = foldM_(xp.cont, (e) => chain_(succeedWith(() => {
                        environments = environments === null || environments === void 0 ? void 0 : environments.previous;
                    }), () => fail(e)), (a) => chain_(succeedWith(() => {
                        environments = environments === null || environments === void 0 ? void 0 : environments.previous;
                    }), () => succeed(a)));
                    break;
                }
                case "Modify": {
                    const updated = xp.run(s0);
                    s0 = updated.get(0);
                    a = updated.get(1);
                    const nextInst = this.pop();
                    if (nextInst) {
                        curXPure = nextInst.apply(a);
                    }
                    else {
                        curXPure = undefined;
                    }
                    break;
                }
            }
        }
        if (failed) {
            return Tp.tuple(logs, E.left(a));
        }
        return Tp.tuple(logs, E.right(Tp.tuple(s0, a)));
    }
}
/**
 * Runs this computation with the specified initial state, returning both the
 * log and either all the failures that occurred or the updated state and the
 * result.
 */
function runAll_(self, s) {
    return new Runtime().runAll(self, s);
}
exports.runAll_ = runAll_;
/**
 * Runs this computation with the specified initial state, returning either a
 * failure or the updated state and the result
 */
function runAll(s) {
    return (self) => runAll_(self, s);
}
exports.runAll = runAll;
/**
 * Runs this computation to produce its result.
 */
function run(self) {
    return runState_(self, undefined).get(1);
}
exports.run = run;
/**
 * Runs this computation with the specified initial state, returning both
 * the updated state and the result.
 */
function runState_(self, s) {
    const result = new Runtime().runAll(self, s).get(1);
    if (result._tag === "Left") {
        throw result.left;
    }
    return result.right;
}
exports.runState_ = runState_;
/**
 * Runs this computation with the specified initial state, returning both
 * the updated state and the result.
 *
 * @ets_data_first runState_
 */
function runState(s) {
    return (self) => runState_(self, s);
}
exports.runState = runState;
/**
 * Runs this computation to produce its result or the first failure to
 * occur.
 */
function runEither(self) {
    return E.map_(new Runtime().runAll(self, undefined).get(1), (x) => x.get(1));
}
exports.runEither = runEither;
/**
 * Runs this computation to produce its result and the log.
 */
function runLog(self) {
    const result = new Runtime().runAll(self, undefined);
    const e = result.get(1);
    if (e._tag === "Left") {
        throw e.left;
    }
    return Tp.tuple(result.get(0), e.right.get(1));
}
exports.runLog = runLog;
/**
 * Runs this computation with the specified initial state, returning the
 * result and discarding the updated state.
 */
function runResult_(self, s) {
    return runState_(self, s)[1];
}
exports.runResult_ = runResult_;
/**
 * Runs this computation with the specified initial state, returning the
 * result and discarding the updated state.
 */
function runResult(s) {
    return (self) => runResult_(self, s);
}
exports.runResult = runResult;
//# sourceMappingURL=core.js.map