"use strict";
// adapted from http://okmij.org/ftp/Computation/free-monad.html
// and https://github.com/purescript/purescript-free
Object.defineProperty(exports, "__esModule", { value: true });
var function_1 = require("./function");
exports.URI = 'Free';
var Pure = /** @class */ (function () {
    function Pure(value) {
        this.value = value;
        this._tag = 'Pure';
    }
    Pure.prototype.map = function (f) {
        return new Pure(f(this.value));
    };
    Pure.prototype.ap = function (fab) {
        var _this = this;
        return fab.chain(function (f) { return _this.map(f); }); // <- derived
    };
    /**
     * Flipped version of {@link ap}
     */
    Pure.prototype.ap_ = function (fb) {
        return fb.ap(this);
    };
    Pure.prototype.chain = function (f) {
        return f(this.value);
    };
    Pure.prototype.inspect = function () {
        return this.toString();
    };
    Pure.prototype.toString = function () {
        return "new Pure(" + function_1.toString(this.value) + ")";
    };
    Pure.prototype.isPure = function () {
        return true;
    };
    Pure.prototype.isImpure = function () {
        return false;
    };
    return Pure;
}());
exports.Pure = Pure;
var Impure = /** @class */ (function () {
    function Impure(fx, f) {
        this.fx = fx;
        this.f = f;
        this._tag = 'Impure';
    }
    Impure.prototype.map = function (f) {
        var _this = this;
        return new Impure(this.fx, function (x) { return _this.f(x).map(f); });
    };
    Impure.prototype.ap = function (fab) {
        var _this = this;
        return fab.chain(function (f) { return _this.map(f); }); // <- derived
    };
    Impure.prototype.ap_ = function (fb) {
        return fb.ap(this);
    };
    Impure.prototype.chain = function (f) {
        var _this = this;
        return new Impure(this.fx, function (x) { return _this.f(x).chain(f); });
    };
    Impure.prototype.inspect = function () {
        return this.toString();
    };
    Impure.prototype.toString = function () {
        return "new Impure(" + (function_1.toString(this.fx), function_1.toString(this.f)) + ")";
    };
    Impure.prototype.isPure = function () {
        return false;
    };
    Impure.prototype.isImpure = function () {
        return true;
    };
    return Impure;
}());
exports.Impure = Impure;
/**
 * @function
 * @since 1.0.0
 */
exports.of = function (a) {
    return new Pure(a);
};
/**
 * Lift an impure value described by the generating type constructor `F` into the free monad
 * @function
 * @since 1.0.0
 */
exports.liftF = function (fa) {
    return new Impure(fa, function (a) { return exports.of(a); });
};
var substFree = function (f) {
    function go(fa) {
        switch (fa._tag) {
            case 'Pure':
                return exports.of(fa.value);
            case 'Impure':
                return f(fa.fx).chain(function (x) { return go(fa.f(x)); });
        }
    }
    return go;
};
function hoistFree(nt) {
    return substFree(function (fa) { return exports.liftF(nt(fa)); });
}
exports.hoistFree = hoistFree;
function foldFree(M) {
    return function (nt, fa) {
        if (fa.isPure()) {
            return M.of(fa.value);
        }
        else {
            return M.chain(nt(fa.fx), function (x) { return foldFree(M)(nt, fa.f(x)); });
        }
    };
}
exports.foldFree = foldFree;
