"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.exposeLeftover = exports.zipWith = exports.zipWith_ = exports.zipRight = exports.zipRight_ = exports.zipLeft = exports.zipLeft_ = exports.zip = exports.zip_ = exports.orElse = exports.orElse_ = exports.summarized = exports.summarized_ = exports.repeat = exports.timed = exports.mapEffect = exports.mapEffect_ = exports.mapError = exports.mapError_ = exports.map = exports.map_ = exports.foldSink = exports.foldSink_ = exports.chain = exports.chain_ = exports.filterInputEffect = exports.filterInputEffect_ = exports.filterInput = exports.filterInput_ = exports.dimapEffect = exports.dimapEffect_ = exports.dimapChunksEffect = exports.dimapChunksEffect_ = exports.dimapChunks = exports.dimapChunks_ = exports.dimap = exports.dimap_ = exports.contramapEffect = exports.contramapEffect_ = exports.contramapChunksEffect = exports.contramapChunksEffect_ = exports.contramapChunks = exports.contramapChunks_ = exports.contramap = exports.contramap_ = exports.collectAllWhileWith = exports.collectAllWhileWith_ = exports.as = exports.as_ = exports.Sink = void 0;
exports.reduceEffect = exports.reduce = exports.take = exports.sum = exports.succeed = exports.managed = exports.managed_ = exports.mkString = exports.leftover = exports.last = exports.head = exports.fromEffect = exports.forEachChunkWhile = exports.forEachWhile = exports.forEachChunk = exports.forEach = exports.foldEffect = exports.foldWeightedEffect = exports.foldWeightedDecomposeEffect = exports.foldWeightedDecompose = exports.foldWeighted = exports.foldUntilEffect = exports.foldUntil = exports.foldLeftEffect = exports.foldLeftChunksEffect = exports.foldLeftChunks = exports.foldLeft = exports.foldChunksEffect = exports.foldChunks = exports.fold = exports.failCause = exports.fail = exports.succeedWith = exports.suspend = exports.drain = exports.dieMessage = exports.die = exports.count = exports.collectAllWhileEffect = exports.collectAllWhile = exports.collectAllToSetN = exports.collectAllToSet = exports.collectAllToMapN = exports.collectAllToMap = exports.collectAllN = exports.collectAll = exports.accessSink = exports.dropLeftover = void 0;
// ets_tracing: off
require("../../../Operator");
const CS = __importStar(require("../../../Cause"));
const CL = __importStar(require("../../../Clock"));
const A = __importStar(require("../../../Collections/Immutable/Chunk"));
const HM = __importStar(require("../../../Collections/Immutable/HashMap"));
const HS = __importStar(require("../../../Collections/Immutable/HashSet"));
const L = __importStar(require("../../../Collections/Immutable/List"));
const Tp = __importStar(require("../../../Collections/Immutable/Tuple"));
const T = __importStar(require("../../../Effect"));
const Function_1 = require("../../../Function");
const O = __importStar(require("../../../Option"));
const Ref = __importStar(require("../../../Ref"));
const AtomicReference_1 = require("../../../Support/AtomicReference");
const C = __importStar(require("../Channel"));
/**
 * Sink is a data type that represent a channel that reads elements
 * of type `In`, handles input errors of type `InErr`, emits errors
 * of type `OutErr`, emits outputs of type `L` and ends with a value
 * of type `Z`.
 */
class Sink {
    constructor(channel) {
        this.channel = channel;
    }
}
exports.Sink = Sink;
/**
 * Replaces this sink's result with the provided value.
 */
function as_(self, z) {
    return map_(self, (_) => z);
}
exports.as_ = as_;
/**
 * Replaces this sink's result with the provided value.
 *
 * @ets_data_first as_
 */
function as(z) {
    return (self) => as_(self, z);
}
exports.as = as;
/**
 * Repeatedly runs the sink for as long as its results satisfy
 * the predicate `p`. The sink's results will be accumulated
 * using the stepping function `f`.
 */
function collectAllWhileWith_(self, z, p, f) {
    return new Sink((C.chain_(C.fromEffect(T.zip_(Ref.makeRef(A.empty()), Ref.makeRef(false))), ({ tuple: [leftoversRef, upstreamDoneRef] }) => {
        const upstreamMarker = C.readWith((in_) => C.zipRight_(C.write(in_), upstreamMarker), (_) => C.fail(_), (x) => C.as_(C.fromEffect(upstreamDoneRef.set(true)), x));
        const loop = (currentResult) => C.foldChannel_(C.doneCollect(self.channel), (_) => C.fail(_), ({ tuple: [leftovers, doneValue] }) => {
            if (p(doneValue)) {
                return (C.map_(C.bind("result", ({ accumulatedResult, upstreamDone }) => upstreamDone
                    ? C.as_(C.write(A.flatten(leftovers)), currentResult)
                    : loop(accumulatedResult))(C.let("accumulatedResult", () => f(currentResult, doneValue))(C.bind("upstreamDone", () => C.fromEffect(upstreamDoneRef.get))(C.fromEffect(leftoversRef.set(A.flatten(leftovers)))))), ({ result }) => result));
            }
            else {
                return C.as_(C.write(A.flatten(leftovers)), currentResult);
            }
        });
        return upstreamMarker[">>>"](C.bufferChunk(leftoversRef))[">>>"](loop(z));
    })));
}
exports.collectAllWhileWith_ = collectAllWhileWith_;
/**
 * Repeatedly runs the sink for as long as its results satisfy
 * the predicate `p`. The sink's results will be accumulated
 * using the stepping function `f`.
 *
 * @ets_data_first collectAllWhileWith_
 */
function collectAllWhileWith(z, p, f) {
    return (self) => collectAllWhileWith_(self, z, p, f);
}
exports.collectAllWhileWith = collectAllWhileWith;
/**
 * Transforms this sink's input elements.
 */
function contramap_(self, f) {
    return contramapChunks_(self, A.map(f));
}
exports.contramap_ = contramap_;
/**
 * Transforms this sink's input elements.
 *
 * @ets_data_first contramap_
 */
function contramap(f) {
    return (self) => contramap_(self, f);
}
exports.contramap = contramap;
/**
 * Transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */
function contramapChunks_(self, f) {
    const loop = C.readWith((chunk) => C.zipRight_(C.write(f(chunk)), loop), (_) => C.fail(_), (_) => C.succeed(_));
    return new Sink(loop[">>>"](self.channel));
}
exports.contramapChunks_ = contramapChunks_;
/**
 * Transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 *
 * @ets_data_first contramapChunks_
 */
function contramapChunks(f) {
    return (self) => contramapChunks_(self, f);
}
exports.contramapChunks = contramapChunks;
/**
 * Effectfully transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */
function contramapChunksEffect_(self, f) {
    const loop = C.readWith((chunk) => C.zipRight_(C.chain_(C.fromEffect(f(chunk)), C.write), loop), (_) => C.fail(_), (_) => C.succeed(_));
    return new Sink(loop[">>>"](self.channel));
}
exports.contramapChunksEffect_ = contramapChunksEffect_;
/**
 * Effectfully transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 *
 * @ets_data_first contramapChunksEffect_
 */
function contramapChunksEffect(f) {
    return (self) => contramapChunksEffect_(self, f);
}
exports.contramapChunksEffect = contramapChunksEffect;
function collectLoop(state) {
    return C.readWithCause((i) => collectLoop(A.concat_(state, i)), C.failCause, (_) => C.end(state));
}
/**
 * Effectfully transforms this sink's input elements.
 */
function contramapEffect_(self, f) {
    return contramapChunksEffect_(self, A.mapM(f));
}
exports.contramapEffect_ = contramapEffect_;
/**
 * Effectfully transforms this sink's input elements.
 *
 * @ets_data_first contramapEffect_
 */
function contramapEffect(f) {
    return (self) => contramapEffect_(self, f);
}
exports.contramapEffect = contramapEffect;
/**
 * Transforms both inputs and result of this sink using the provided functions.
 */
function dimap_(self, f, g) {
    return map_(contramap_(self, f), g);
}
exports.dimap_ = dimap_;
/**
 * Transforms both inputs and result of this sink using the provided functions.
 *
 * @ets_data_first dimap_
 */
function dimap(f, g) {
    return (self) => dimap_(self, f, g);
}
exports.dimap = dimap;
/**
 * Transforms both input chunks and result of this sink using the provided functions.
 */
function dimapChunks_(self, f, g) {
    return map_(contramapChunks_(self, f), g);
}
exports.dimapChunks_ = dimapChunks_;
/**
 * Transforms both input chunks and result of this sink using the provided functions.
 *
 * @ets_data_first dimapChunks_
 */
function dimapChunks(f, g) {
    return (self) => dimapChunks_(self, f, g);
}
exports.dimapChunks = dimapChunks;
/**
 * Effectfully transforms both input chunks and result of this sink using the provided functions.
 * `f` and `g` must preserve chunking-invariance
 */
function dimapChunksEffect_(self, f, g) {
    return mapEffect_(contramapChunksEffect_(self, f), g);
}
exports.dimapChunksEffect_ = dimapChunksEffect_;
/**
 * Effectfully transforms both input chunks and result of this sink using the provided functions.
 * `f` and `g` must preserve chunking-invariance
 *
 * @ets_data_first dimapChunksEffect_
 */
function dimapChunksEffect(f, g) {
    return (self) => dimapChunksEffect_(self, f, g);
}
exports.dimapChunksEffect = dimapChunksEffect;
/**
 * Effectfully transforms both inputs and result of this sink using the provided functions.
 */
function dimapEffect_(self, f, g) {
    return mapEffect_(contramapEffect_(self, f), g);
}
exports.dimapEffect_ = dimapEffect_;
/**
 * Effectfully transforms both inputs and result of this sink using the provided functions.
 *
 * @ets_data_first dimapEffect_
 */
function dimapEffect(f, g) {
    return (self) => dimapEffect_(self, f, g);
}
exports.dimapEffect = dimapEffect;
function filterInput_(self, p) {
    return contramapChunks_(self, A.filter(p));
}
exports.filterInput_ = filterInput_;
function filterInput(p) {
    return (self) => filterInput_(self, p);
}
exports.filterInput = filterInput;
function filterInputEffect_(self, p) {
    return contramapChunksEffect_(self, A.filterM(p));
}
exports.filterInputEffect_ = filterInputEffect_;
/**
 * @ets_data_first filterInputEffect_
 */
function filterInputEffect(p) {
    return (self) => filterInputEffect_(self, p);
}
exports.filterInputEffect = filterInputEffect;
/**
 * Runs this sink until it yields a result, then uses that result to create another
 * sink from the provided function which will continue to run until it yields a result.
 *
 * This function essentially runs sinks in sequence.
 */
function chain_(self, f) {
    return foldSink_(self, (_) => fail(_), f);
}
exports.chain_ = chain_;
/**
 * Runs this sink until it yields a result, then uses that result to create another
 * sink from the provided function which will continue to run until it yields a result.
 *
 * This function essentially runs sinks in sequence.
 *
 * @ets_data_first chain_
 */
function chain(f) {
    return (self) => chain_(self, f);
}
exports.chain = chain;
function foldSink_(self, failure, success) {
    return new Sink(C.foldChannel_(C.doneCollect(self.channel), (_) => failure(_).channel, ({ tuple: [leftovers, z] }) => C.suspend(() => {
        const leftoversRef = new AtomicReference_1.AtomicReference(A.filter_(leftovers, (a) => !A.isEmpty(a)));
        const refReader = C.chain_(C.succeedWith(() => leftoversRef.getAndSet(A.empty())), (chunk) => C.writeChunk(chunk));
        const passthrough = C.identity();
        const continationSink = C.zipRight_(refReader, passthrough)[">>>"](success(z).channel);
        return C.chain_(C.doneCollect(continationSink), ({ tuple: [newLeftovers, z1] }) => C.zipRight_(C.chain_(C.succeedWith(() => leftoversRef.get), (_) => C.writeChunk(_)), C.as_(C.writeChunk(newLeftovers), z1)));
    })));
}
exports.foldSink_ = foldSink_;
/**
 *
 * @ets_data_first foldSink_
 */
function foldSink(failure, success) {
    return (self) => foldSink_(self, failure, success);
}
exports.foldSink = foldSink;
/**
 * Transforms this sink's result.
 */
function map_(self, f) {
    return new Sink(C.map_(self.channel, f));
}
exports.map_ = map_;
/**
 * Transforms this sink's result.
 *
 * @ets_data_first map_
 */
function map(f) {
    return (self) => map_(self, f);
}
exports.map = map;
/**
 * Transforms the errors emitted by this sink using `f`.
 */
function mapError_(self, f) {
    return new Sink(C.mapError_(self.channel, f));
}
exports.mapError_ = mapError_;
/**
 * Transforms the errors emitted by this sink using `f`.
 *
 * @ets_data_first mapError_
 */
function mapError(f) {
    return (self) => mapError_(self, f);
}
exports.mapError = mapError;
/**
 * Effectfully transforms this sink's result.
 */
function mapEffect_(self, f) {
    return new Sink(C.mapEffect_(self.channel, f));
}
exports.mapEffect_ = mapEffect_;
/**
 * Effectfully transforms this sink's result.
 *
 * @ets_data_first mapEffect_
 */
function mapEffect(f) {
    return (self) => mapEffect_(self, f);
}
exports.mapEffect = mapEffect;
// TODO: race -> Missing raceBoth
// TODO: raceBoth -> Not implemented
/**
 * Returns the sink that executes this one and times its execution.
 */
function timed(self) {
    return summarized_(self, CL.currentTime, (start, end) => end - start);
}
exports.timed = timed;
function repeat(self) {
    return collectAllWhileWith_(self, A.empty(), (_) => true, (s, z) => A.append_(s, z));
}
exports.repeat = repeat;
/**
 * Summarize a sink by running an effect when the sink starts and again when it completes
 */
function summarized_(self, summary, f) {
    return new Sink((C.map_(C.bind("end", () => C.fromEffect(summary))(C.bind("done", () => self.channel)(C.bind("start", () => C.fromEffect(summary))(C.do))), ({ done, end, start }) => Tp.tuple(done, f(start, end)))));
}
exports.summarized_ = summarized_;
/**
 * Summarize a sink by running an effect when the sink starts and again when it completes
 * @ets_data_first summarized_
 */
function summarized(summary, f) {
    return (self) => summarized_(self, summary, f);
}
exports.summarized = summarized;
function orElse_(self, that) {
    return new Sink(C.orElse_(self.channel, that.channel));
}
exports.orElse_ = orElse_;
/**
 * @ets_data_first orElse_
 */
function orElse(that) {
    return (self) => orElse_(self, that);
}
exports.orElse = orElse;
function zip_(self, that) {
    return zipWith_(self, that, (a, b) => Tp.tuple(a, b));
}
exports.zip_ = zip_;
/**
 * @ets_data_first zip_
 */
function zip(that) {
    return (self) => zip_(self, that);
}
exports.zip = zip;
/**
 * Like `zip`, but keeps only the result from the `that` sink.
 */
function zipLeft_(self, that) {
    return zipWith_(self, that, (z, _) => z);
}
exports.zipLeft_ = zipLeft_;
/**
 * Like `zip`, but keeps only the result from `that sink.
 *
 * @ets_data_first zipLeft_
 */
function zipLeft(that) {
    return (self) => zipLeft_(self, that);
}
exports.zipLeft = zipLeft;
// TODO: zipPar -> Missing zipWithPar
// TODO: zipParLeft -> Missing zipWithPar
// TODO: zipParRight -> Missing zipWithPar
// TODO: zipWithPar -> Not implemented
/**
 * Like `zip`, but keeps only the result from this sink.
 */
function zipRight_(self, that) {
    return zipWith_(self, that, (_, z1) => z1);
}
exports.zipRight_ = zipRight_;
/**
 * Like `zip`, but keeps only the result from this sink.
 *
 * @ets_data_first zipRight_
 */
function zipRight(that) {
    return (self) => zipRight_(self, that);
}
exports.zipRight = zipRight;
/**
 * Feeds inputs to this sink until it yields a result, then switches over to the
 * provided sink until it yields a result, finally combining the two results with `f`.
 */
function zipWith_(self, that, f) {
    return chain_(self, (z) => map_(that, (_) => f(z, _)));
}
exports.zipWith_ = zipWith_;
/**
 * Feeds inputs to this sink until it yields a result, then switches over to the
 * provided sink until it yields a result, finally combining the two results with `f`.
 *
 * @ets_data_first zipWith_
 */
function zipWith(that, f) {
    return (self) => zipWith_(self, that, f);
}
exports.zipWith = zipWith;
function exposeLeftover(self) {
    return new Sink(C.map_(C.doneCollect(self.channel), ({ tuple: [chunks, z] }) => Tp.tuple(z, A.flatten(chunks))));
}
exports.exposeLeftover = exposeLeftover;
function dropLeftover(self) {
    return new Sink(C.drain(self.channel));
}
exports.dropLeftover = dropLeftover;
// TODO: untilOutputEffect_ -> Not implemented
function accessSink(f) {
    return new Sink(C.unwrap(T.access((_) => f(_).channel)));
}
exports.accessSink = accessSink;
/**
 * A sink that collects all of its inputs into a chunk.
 */
function collectAll() {
    return new Sink(collectLoop(A.empty()));
}
exports.collectAll = collectAll;
/**
 * A sink that collects first `n` elements into a chunk. Note that the chunk
 * is preallocated and must fit in memory.
 */
function collectAllN(n) {
    return (map((_) => _.build())(chain((cb) => foldUntil(cb, n, (s, in_) => s.append(in_)))(fromEffect(T.succeedWith(() => A.builder())))));
}
exports.collectAllN = collectAllN;
/**
 * A sink that collects all of its inputs into a map. The keys are extracted from inputs
 * using the keying function `key`; if multiple inputs use the same key, they are merged
 * using the `f` function.
 */
function collectAllToMap(key, f) {
    return foldLeftChunks(HM.make(), (acc, as) => A.reduce_(as, acc, (acc, a) => {
        const k = key(a);
        if (HM.has_(acc, k)) {
            return HM.update_(acc, k, (v) => f(v, a));
        }
        else {
            return HM.set_(acc, k, a);
        }
    }));
}
exports.collectAllToMap = collectAllToMap;
/**
 * A sink that collects first `n` keys into a map. The keys are calculated
 * from inputs using the keying function `key`; if multiple inputs use the
 * the same key, they are merged using the `f` function.
 */
function collectAllToMapN(n, key, f) {
    return foldWeighted(HM.make(), (acc, in_) => (HM.has_(acc, key(in_)) ? 0 : 1), n, (acc, in_) => {
        const k = key(in_);
        if (HM.has_(acc, k)) {
            return HM.update_(acc, k, (v) => f(v, in_));
        }
        else {
            return HM.set_(acc, k, in_);
        }
    });
}
exports.collectAllToMapN = collectAllToMapN;
/**
 * A sink that collects all of its inputs into a set.
 */
function collectAllToSet() {
    return foldLeftChunks(HS.make(), (acc, as) => A.reduce_(as, acc, (s, a) => HS.add_(s, a)));
}
exports.collectAllToSet = collectAllToSet;
/**
 * A sink that collects first `n` distinct inputs into a set.
 */
function collectAllToSetN(n) {
    return foldWeighted(HS.make(), (acc, in_) => (HS.has_(acc, in_) ? 0 : 1), n, (s, a) => HS.add_(s, a));
}
exports.collectAllToSetN = collectAllToSetN;
/**
 * Accumulates incoming elements into a chunk as long as they verify predicate `p`.
 */
function collectAllWhile(p) {
    return (map(({ tuple: [is, _] }) => A.from(L.reverse(is)))(fold(Tp.tuple(L.empty(), true), Tp.get(1), ({ tuple: [as, _] }, a) => {
        if (p(a)) {
            return Tp.tuple(L.prepend_(as, a), true);
        }
        else {
            return Tp.tuple(as, false);
        }
    })));
}
exports.collectAllWhile = collectAllWhile;
/**
 * Accumulates incoming elements into a chunk as long as they verify effectful predicate `p`.
 */
function collectAllWhileEffect(p) {
    return (map(({ tuple: [is, _] }) => A.from(L.reverse(is)))(foldEffect(Tp.tuple(L.empty(), true), Tp.get(1), ({ tuple: [as, _] }, a) => T.map_(p(a), (_) => {
        if (_) {
            return Tp.tuple(L.prepend_(as, a), true);
        }
        else {
            return Tp.tuple(as, false);
        }
    }))));
}
exports.collectAllWhileEffect = collectAllWhileEffect;
/**
 * A sink that counts the number of elements fed to it.
 */
function count() {
    return foldLeft(0, (s, _) => s + 1);
}
exports.count = count;
/**
 * Creates a sink halting with the specified `Throwable`.
 */
function die(e) {
    return failCause(CS.die(e));
}
exports.die = die;
/**
 * Creates a sink halting with the specified message, wrapped in a
 * `RuntimeException`.
 */
function dieMessage(message) {
    return failCause(CS.die(new CS.RuntimeError(message)));
}
exports.dieMessage = dieMessage;
/**
 * A sink that ignores all of its inputs.
 */
function drain() {
    const drain = C.readWithCause((_) => drain, C.failCause, (_) => C.unit);
    return new Sink(drain);
}
exports.drain = drain;
/**
 * Returns a lazily constructed sink that may require effects for its creation.
 */
function suspend(f) {
    return new Sink(C.suspend(() => f().channel));
}
exports.suspend = suspend;
/**
 * Returns a sink that executes a total effect and ends with its result.
 */
function succeedWith(effect) {
    return new Sink(C.succeedWith(effect));
}
exports.succeedWith = succeedWith;
/**
 * A sink that always fails with the specified error.
 */
function fail(e) {
    return new Sink(C.fail(e));
}
exports.fail = fail;
/**
 * Creates a sink halting with a specified cause.
 */
function failCause(e) {
    return new Sink(C.failCause(e));
}
exports.failCause = failCause;
/**
 * A sink that folds its inputs with the provided function, termination predicate and initial state.
 */
function fold(z, contFn, f) {
    const foldChunkSplit = (z, chunk, contFn, f) => {
        const fold = (s, chunk, idx, len) => {
            if (idx === len) {
                return Tp.tuple(s, A.empty());
            }
            else {
                const s1 = f(s, A.unsafeGet_(chunk, idx));
                if (contFn(s1)) {
                    return fold(s1, chunk, idx + 1, len);
                }
                else {
                    return Tp.tuple(s1, A.drop_(chunk, idx + 1));
                }
            }
        };
        return fold(z, chunk, 0, A.size(chunk));
    };
    const reader = (s) => {
        if (!contFn(s)) {
            return C.end(s);
        }
        return C.readWith((in_) => {
            const { tuple: [nextS, leftovers] } = foldChunkSplit(s, in_, contFn, f);
            if (!A.isEmpty(leftovers)) {
                return C.as_(C.write(leftovers), nextS);
            }
            else {
                return reader(nextS);
            }
        }, (err) => C.fail(err), (_) => C.end(s));
    };
    return new Sink(reader(z));
}
exports.fold = fold;
/**
 * A sink that folds its input chunks with the provided function, termination predicate and initial state.
 * `contFn` condition is checked only for the initial value and at the end of processing of each chunk.
 * `f` and `contFn` must preserve chunking-invariance.
 */
function foldChunks(z, contFn, f) {
    const reader = (s) => C.readWith((in_) => {
        const nextS = f(s, in_);
        return contFn(nextS) ? reader(nextS) : C.end(nextS);
    }, (err) => C.fail(err), (_) => C.end(s));
    return new Sink(contFn(z) ? reader(z) : C.end(z));
}
exports.foldChunks = foldChunks;
/**
 * A sink that effectfully folds its input chunks with the provided function, termination predicate and initial state.
 * `contFn` condition is checked only for the initial value and at the end of processing of each chunk.
 * `f` and `contFn` must preserve chunking-invariance.
 */
function foldChunksEffect(z, contFn, f) {
    const reader = (s) => C.readWith((in_) => C.chain_(C.fromEffect(f(s, in_)), (nextS) => {
        if (contFn(nextS)) {
            return reader(nextS);
        }
        else {
            return C.end(nextS);
        }
    }), (err) => C.fail(err), (_) => C.end(s));
    return new Sink(contFn(z) ? reader(z) : C.end(z));
}
exports.foldChunksEffect = foldChunksEffect;
/**
 * A sink that folds its inputs with the provided function and initial state.
 */
function foldLeft(z, f) {
    return dropLeftover(fold(z, (_) => true, f));
}
exports.foldLeft = foldLeft;
/**
 * A sink that folds its input chunks with the provided function and initial state.
 * `f` must preserve chunking-invariance.
 */
function foldLeftChunks(z, f) {
    return foldChunks(z, (_) => true, f);
}
exports.foldLeftChunks = foldLeftChunks;
/**
 * A sink that effectfully folds its input chunks with the provided function and initial state.
 * `f` must preserve chunking-invariance.
 */
function foldLeftChunksEffect(z, f) {
    return dropLeftover(foldChunksEffect(z, (_) => true, f));
}
exports.foldLeftChunksEffect = foldLeftChunksEffect;
/**
 * A sink that effectfully folds its inputs with the provided function and initial state.
 */
function foldLeftEffect(z, f) {
    return foldEffect(z, (_) => true, f);
}
exports.foldLeftEffect = foldLeftEffect;
/**
 * Creates a sink that folds elements of type `In` into a structure
 * of type `S` until `max` elements have been folded.
 *
 * Like `foldWeighted`, but with a constant cost function of 1.
 */
function foldUntil(z, max, f) {
    return map_(fold(Tp.tuple(z, 0), (_) => Tp.get_(_, 1) < max, ({ tuple: [o, count] }, i) => Tp.tuple(f(o, i), count + 1)), Tp.get(0));
}
exports.foldUntil = foldUntil;
/**
 * Creates a sink that effectfully folds elements of type `In` into a structure
 * of type `S` until `max` elements have been folded.
 *
 * Like `foldWeightedM`, but with a constant cost function of 1.
 */
function foldUntilEffect(z, max, f) {
    return (map(Tp.get(0))(foldEffect(Tp.tuple(z, 0), ({ tuple: [_, a] }) => a < max, ({ tuple: [o, count] }, i) => T.map_(f(o, i), (_) => Tp.tuple(_, count + 1)))));
}
exports.foldUntilEffect = foldUntilEffect;
/**
 * Creates a sink that folds elements of type `In` into a structure
 * of type `S`, until `max` worth of elements (determined by the `costFn`)
 * have been folded.
 *
 * @note Elements that have an individual cost larger than `max` will
 * force the sink to cross the `max` cost. See `foldWeightedDecompose`
 * for a variant that can handle these cases.
 */
function foldWeighted(z, costFn, max, f) {
    return foldWeightedDecompose(z, costFn, max, (_) => A.single(_), f);
}
exports.foldWeighted = foldWeighted;
/**
 * Creates a sink that folds elements of type `In` into a structure
 * of type `S`, until `max` worth of elements (determined by the `costFn`)
 * have been folded.
 *
 * The `decompose` function will be used for decomposing elements that
 * cause an `S` aggregate to cross `max` into smaller elements.
 * Be vigilant with this function, it has to generate "simpler" values
 * or the fold may never end. A value is considered indivisible if
 * `decompose` yields the empty chunk or a single-valued chunk. In
 * these cases, there is no other choice than to yield a value that
 * will cross the threshold.
 *
 * The `foldWeightedDecomposeM` allows the decompose function
 * to return an `Effect` value, and consequently it allows the sink
 * to fail.
 */
function foldWeightedDecompose(z, costFn, max, decompose, f) {
    const go = (s, cost, dirty) => C.readWith((in_) => {
        const fold = (in_, s, dirty, cost, idx) => {
            if (idx === A.size(in_)) {
                return Tp.tuple(s, cost, dirty, A.empty());
            }
            else {
                const elem = A.unsafeGet_(in_, idx);
                const total = cost + costFn(s, elem);
                if (total <= max) {
                    return fold(in_, f(s, elem), true, total, idx + 1);
                }
                else {
                    const decomposed = decompose(elem);
                    if (A.size(decomposed) <= 1 && !dirty) {
                        return Tp.tuple(f(s, elem), total, true, A.drop_(in_, idx + 1));
                    }
                    else if (A.size(in_) <= 1 && dirty) {
                        return Tp.tuple(s, cost, dirty, A.drop_(in_, idx));
                    }
                    else {
                        return fold(A.concat_(decomposed, A.drop_(in_, idx + 1)), s, dirty, cost, 0);
                    }
                }
            }
        };
        const { tuple: [nextS, nextCost, nextDirty, leftovers] } = fold(in_, s, dirty, cost, 0);
        if (!A.isEmpty(leftovers)) {
            return C.zipRight_(C.write(leftovers), C.end(nextS));
        }
        else if (cost > max) {
            return C.end(nextS);
        }
        else {
            return go(nextS, nextCost, nextDirty);
        }
    }, (err) => C.fail(err), (_) => C.end(s));
    return new Sink(go(z, 0, false));
}
exports.foldWeightedDecompose = foldWeightedDecompose;
/**
 * Creates a sink that effectfully folds elements of type `In` into a structure
 * of type `S`, until `max` worth of elements (determined by the `costFn`) have
 * been folded.
 *
 * The `decompose` function will be used for decomposing elements that
 * cause an `S` aggregate to cross `max` into smaller elements. Be vigilant with
 * this function, it has to generate "simpler" values or the fold may never end.
 * A value is considered indivisible if `decompose` yields the empty chunk or a
 * single-valued chunk. In these cases, there is no other choice than to yield
 * a value that will cross the threshold.
 *
 * See `foldWeightedDecompose` for an example.
 */
function foldWeightedDecomposeEffect(z, costFn, max, decompose, f) {
    const go = (s, cost, dirty) => C.readWith((in_) => {
        const fold = (in_, s, dirty, cost, idx) => {
            if (idx === A.size(in_)) {
                return T.succeed(Tp.tuple(s, cost, dirty, A.empty()));
            }
            else {
                const elem = A.unsafeGet_(in_, idx);
                return (T.chain_(T.map_(costFn(s, elem), (_) => cost + _), (total) => {
                    if (total <= max) {
                        return T.chain_(f(s, elem), (_) => fold(in_, _, true, total, idx + 1));
                    }
                    else {
                        return T.chain_(decompose(elem), (decomposed) => {
                            if (A.size(decomposed) <= 1 && !dirty) {
                                return T.map_(f(s, elem), (_) => Tp.tuple(_, total, true, A.drop_(in_, idx + 1)));
                            }
                            else if (A.size(decomposed) <= 1 && dirty) {
                                return T.succeed(Tp.tuple(s, cost, dirty, A.drop_(in_, idx)));
                            }
                            else {
                                return fold(A.concat_(decomposed, A.drop_(in_, idx + 1)), s, dirty, cost, 0);
                            }
                        });
                    }
                }));
            }
        };
        return (C.chain_(C.fromEffect(fold(in_, s, dirty, cost, 0)), ({ tuple: [nextS, nextCost, nextDirty, leftovers] }) => {
            if (!A.isEmpty(leftovers)) {
                return C.zipRight_(C.write(leftovers), C.end(nextS));
            }
            else if (cost > max) {
                return C.end(nextS);
            }
            else {
                return go(nextS, nextCost, nextDirty);
            }
        }));
    }, (err) => C.fail(err), (_) => C.end(s));
    return new Sink(go(z, 0, false));
}
exports.foldWeightedDecomposeEffect = foldWeightedDecomposeEffect;
/**
 * Creates a sink that effectfully folds elements of type `In` into a structure
 * of type `S`, until `max` worth of elements (determined by the `costFn`) have
 * been folded.
 *
 * @note Elements that have an individual cost larger than `max` will
 * force the sink to cross the `max` cost. See `foldWeightedDecomposeM`
 * for a variant that can handle these cases.
 */
function foldWeightedEffect(z, costFn, max, f) {
    return foldWeightedDecomposeEffect(z, costFn, max, (i) => T.succeed(A.single(i)), f);
}
exports.foldWeightedEffect = foldWeightedEffect;
/**
 * A sink that effectfully folds its inputs with the provided function, termination predicate and initial state.
 */
function foldEffect(z, contFn, f) {
    const foldChunkSplitM = (z, chunk, contFn, f) => {
        const fold = (s, chunk, idx, len) => {
            if (idx === len) {
                return T.succeed(Tp.tuple(s, O.none));
            }
            else {
                return T.chain_(f(s, A.unsafeGet_(chunk, idx)), (s1) => {
                    if (contFn(s1)) {
                        return fold(s1, chunk, idx + 1, len);
                    }
                    else {
                        return T.succeed(Tp.tuple(s1, O.some(A.drop_(chunk, idx + 1))));
                    }
                });
            }
        };
        return fold(z, chunk, 0, A.size(chunk));
    };
    const reader = (s) => C.readWith((in_) => C.chain_(C.fromEffect(foldChunkSplitM(s, in_, contFn, f)), ({ tuple: [nextS, leftovers] }) => O.fold_(leftovers, () => reader(nextS), (l) => C.as_(C.write(l), nextS))), (err) => C.fail(err), (_) => C.end(s));
    return new Sink(contFn(z) ? reader(z) : C.end(z));
}
exports.foldEffect = foldEffect;
/**
 * A sink that executes the provided effectful function for every element fed to it.
 */
function forEach(f) {
    return forEachWhile((_) => T.as_(f(_), true));
}
exports.forEach = forEach;
/**
 * A sink that executes the provided effectful function for every chunk fed to it.
 */
function forEachChunk(f) {
    return forEachChunkWhile((_) => T.as_(f(_), true));
}
exports.forEachChunk = forEachChunk;
/**
 * A sink that executes the provided effectful function for every element fed to it
 * until `f` evaluates to `false`.
 */
function forEachWhile(f) {
    const go = (chunk, idx, len, cont) => {
        if (idx === len) {
            return cont;
        }
        else {
            return (C.catchAll_(C.chain_(C.fromEffect(f(A.unsafeGet_(chunk, idx))), (b) => {
                if (b) {
                    return go(chunk, idx + 1, len, cont);
                }
                else {
                    return C.write(A.drop_(chunk, idx));
                }
            }), (e) => C.zipRight_(C.write(A.drop_(chunk, idx)), C.fail(e))));
        }
    };
    const process = C.readWithCause((_in) => go(_in, 0, A.size(_in), process), (halt) => C.failCause(halt), (_) => C.end(undefined));
    return new Sink(process);
}
exports.forEachWhile = forEachWhile;
/**
 * A sink that executes the provided effectful function for every chunk fed to it
 * until `f` evaluates to `false`.
 */
function forEachChunkWhile(f) {
    const reader = C.readWith((_in) => C.chain_(C.fromEffect(f(_in)), (continue_) => continue_ ? reader : C.end(undefined)), (err) => C.fail(err), (_) => C.unit);
    return new Sink(reader);
}
exports.forEachChunkWhile = forEachChunkWhile;
/**
 * Creates a single-value sink produced from an effect
 */
function fromEffect(b) {
    return new Sink(C.fromEffect(b));
}
exports.fromEffect = fromEffect;
/**
 * Creates a sink containing the first value.
 */
function head() {
    return fold(O.none, O.isNone, (s, in_) => O.fold_(s, () => O.some(in_), (_) => s));
}
exports.head = head;
/**
 * Creates a sink containing the last value.
 */
function last() {
    return foldLeft(O.none, (_, in_) => O.some(in_));
}
exports.last = last;
function leftover(c) {
    return new Sink(C.write(c));
}
exports.leftover = leftover;
function mkString() {
    return suspend(() => {
        const strings = [];
        return (map((_) => strings.join(""))(foldLeftChunks(undefined, (_, els) => A.forEach_(els, (el) => {
            strings.push(String(el));
        }))));
    });
}
exports.mkString = mkString;
function managed_(resource, fn) {
    return new Sink(C.managed_(resource, (_) => fn(_).channel));
}
exports.managed_ = managed_;
/**
 *
 * @ets_data_first managed_
 */
function managed(fn) {
    return (resource) => managed_(resource, fn);
}
exports.managed = managed;
/**
 * A sink that immediately ends with the specified value.
 */
function succeed(z) {
    return new Sink(C.succeed(z));
}
exports.succeed = succeed;
/**
 * A sink that sums incoming numeric values.
 */
function sum() {
    return foldLeft(0, (a, b) => a + b);
}
exports.sum = sum;
/*
 * A sink that takes the specified number of values.
 */
function take(n) {
    return (chain((acc) => {
        const { tuple: [taken, leftover] } = A.splitAt_(acc, n);
        return new Sink(C.zipRight_(C.write(leftover), C.end(taken)));
    })(foldChunks(A.empty(), (_) => A.size(_) < n, (a, b) => A.concat_(a, b))));
}
exports.take = take;
/**
 * A sink that folds its inputs with the provided function, termination predicate and initial state.
 */
function reduce(z, cont, f) {
    const reduceChunkSplit = (z, chunk) => (cont) => (f) => {
        const reduce = (s, chunk, idx, len) => {
            if (idx === len) {
                return Tp.tuple(s, A.empty());
            }
            else {
                const s1 = f(s, A.unsafeGet_(chunk, idx));
                if (cont(s1)) {
                    return reduce(s1, chunk, idx + 1, len);
                }
                else {
                    return Tp.tuple(s1, A.drop_(chunk, idx + 1));
                }
            }
        };
        return reduce(z, chunk, 0, A.size(chunk));
    };
    const reader = (s) => {
        if (!cont(s)) {
            return C.end(s);
        }
        else {
            return C.readWith((_in) => {
                const { tuple: [nextS, leftovers] } = reduceChunkSplit(s, _in)(cont)(f);
                if (!A.isEmpty(leftovers)) {
                    return C.as_(C.write(leftovers), nextS);
                }
                else {
                    return reader(nextS);
                }
            }, (err) => C.fail(err), (_) => C.end(s));
        }
    };
    return new Sink(reader(z));
}
exports.reduce = reduce;
/**
 * A sink that effectfully folds its inputs with the provided function, termination predicate and initial state.
 */
function reduceEffect(z, cont, f) {
    const reduceChunkSplit = (z, chunk) => (cont) => (f) => {
        const reduce = (s, chunk, idx, len) => {
            if (idx === len) {
                return T.succeed(Tp.tuple(s, O.none));
            }
            else {
                return T.chain_(f(s, A.unsafeGet_(chunk, idx)), (s1) => {
                    if (cont(s1)) {
                        return reduce(s1, chunk, idx + 1, len);
                    }
                    else {
                        return T.succeed(Tp.tuple(s1, O.some(A.drop_(chunk, idx + 1))));
                    }
                });
            }
        };
        return reduce(z, chunk, 0, A.size(chunk));
    };
    const reader = (s) => {
        if (!cont(s)) {
            return C.end(s);
        }
        else {
            return C.readWith((_in) => {
                return (C.chain_(C.fromEffect(reduceChunkSplit(s, _in)(cont)(f)), ({ tuple: [nextS, leftovers] }) => {
                    return O.fold_(leftovers, () => reader(nextS), (l) => C.as_(C.write(l), nextS));
                }));
            }, (err) => C.fail(err), (_) => C.end(s));
        }
    };
    return new Sink(reader(z));
}
exports.reduceEffect = reduceEffect;
//# sourceMappingURL=index.js.map