// ets_tracing: off
import "../../../Operator";
import * as CS from "../../../Cause";
import * as CL from "../../../Clock";
import * as A from "../../../Collections/Immutable/Chunk";
import * as HM from "../../../Collections/Immutable/HashMap";
import * as HS from "../../../Collections/Immutable/HashSet";
import * as L from "../../../Collections/Immutable/List";
import * as Tp from "../../../Collections/Immutable/Tuple";
import * as T from "../../../Effect";
import { pipe } from "../../../Function";
import * as O from "../../../Option";
import * as Ref from "../../../Ref";
import { AtomicReference } from "../../../Support/AtomicReference";
import * as C from "../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`.
 */

export class Sink {
  constructor(channel) {
    this.channel = channel;
  }

}
/**
 * Replaces this sink's result with the provided value.
 */

export function as_(self, z) {
  return map_(self, _ => z);
}
/**
 * Replaces this sink's result with the provided value.
 *
 * @ets_data_first as_
 */

export function as(z) {
  return self => as_(self, z);
}
/**
 * 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`.
 */

export 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));
  }));
}
/**
 * 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_
 */

export function collectAllWhileWith(z, p, f) {
  return self => collectAllWhileWith_(self, z, p, f);
}
/**
 * Transforms this sink's input elements.
 */

export function contramap_(self, f) {
  return contramapChunks_(self, A.map(f));
}
/**
 * Transforms this sink's input elements.
 *
 * @ets_data_first contramap_
 */

export function contramap(f) {
  return self => contramap_(self, f);
}
/**
 * Transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */

export 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));
}
/**
 * Transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 *
 * @ets_data_first contramapChunks_
 */

export function contramapChunks(f) {
  return self => contramapChunks_(self, f);
}
/**
 * Effectfully transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */

export 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));
}
/**
 * Effectfully transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 *
 * @ets_data_first contramapChunksEffect_
 */

export function contramapChunksEffect(f) {
  return self => contramapChunksEffect_(self, f);
}

function collectLoop(state) {
  return C.readWithCause(i => collectLoop(A.concat_(state, i)), C.failCause, _ => C.end(state));
}
/**
 * Effectfully transforms this sink's input elements.
 */


export function contramapEffect_(self, f) {
  return contramapChunksEffect_(self, A.mapM(f));
}
/**
 * Effectfully transforms this sink's input elements.
 *
 * @ets_data_first contramapEffect_
 */

export function contramapEffect(f) {
  return self => contramapEffect_(self, f);
}
/**
 * Transforms both inputs and result of this sink using the provided functions.
 */

export function dimap_(self, f, g) {
  return map_(contramap_(self, f), g);
}
/**
 * Transforms both inputs and result of this sink using the provided functions.
 *
 * @ets_data_first dimap_
 */

export function dimap(f, g) {
  return self => dimap_(self, f, g);
}
/**
 * Transforms both input chunks and result of this sink using the provided functions.
 */

export function dimapChunks_(self, f, g) {
  return map_(contramapChunks_(self, f), g);
}
/**
 * Transforms both input chunks and result of this sink using the provided functions.
 *
 * @ets_data_first dimapChunks_
 */

export function dimapChunks(f, g) {
  return self => dimapChunks_(self, f, g);
}
/**
 * Effectfully transforms both input chunks and result of this sink using the provided functions.
 * `f` and `g` must preserve chunking-invariance
 */

export function dimapChunksEffect_(self, f, g) {
  return mapEffect_(contramapChunksEffect_(self, f), g);
}
/**
 * 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_
 */

export function dimapChunksEffect(f, g) {
  return self => dimapChunksEffect_(self, f, g);
}
/**
 * Effectfully transforms both inputs and result of this sink using the provided functions.
 */

export function dimapEffect_(self, f, g) {
  return mapEffect_(contramapEffect_(self, f), g);
}
/**
 * Effectfully transforms both inputs and result of this sink using the provided functions.
 *
 * @ets_data_first dimapEffect_
 */

export function dimapEffect(f, g) {
  return self => dimapEffect_(self, f, g);
}
export function filterInput_(self, p) {
  return contramapChunks_(self, A.filter(p));
}
export function filterInput(p) {
  return self => filterInput_(self, p);
}
export function filterInputEffect_(self, p) {
  return contramapChunksEffect_(self, A.filterM(p));
}
/**
 * @ets_data_first filterInputEffect_
 */

export function filterInputEffect(p) {
  return self => filterInputEffect_(self, p);
}
/**
 * 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.
 */

export function chain_(self, f) {
  return foldSink_(self, _ => fail(_), f);
}
/**
 * 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_
 */

export function chain(f) {
  return self => chain_(self, f);
}
export 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(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)));
  })));
}
/**
 *
 * @ets_data_first foldSink_
 */

export function foldSink(failure, success) {
  return self => foldSink_(self, failure, success);
}
/**
 * Transforms this sink's result.
 */

export function map_(self, f) {
  return new Sink(C.map_(self.channel, f));
}
/**
 * Transforms this sink's result.
 *
 * @ets_data_first map_
 */

export function map(f) {
  return self => map_(self, f);
}
/**
 * Transforms the errors emitted by this sink using `f`.
 */

export function mapError_(self, f) {
  return new Sink(C.mapError_(self.channel, f));
}
/**
 * Transforms the errors emitted by this sink using `f`.
 *
 * @ets_data_first mapError_
 */

export function mapError(f) {
  return self => mapError_(self, f);
}
/**
 * Effectfully transforms this sink's result.
 */

export function mapEffect_(self, f) {
  return new Sink(C.mapEffect_(self.channel, f));
}
/**
 * Effectfully transforms this sink's result.
 *
 * @ets_data_first mapEffect_
 */

export function mapEffect(f) {
  return self => mapEffect_(self, f);
} // TODO: race -> Missing raceBoth
// TODO: raceBoth -> Not implemented

/**
 * Returns the sink that executes this one and times its execution.
 */

export function timed(self) {
  return summarized_(self, CL.currentTime, (start, end) => end - start);
}
export function repeat(self) {
  return collectAllWhileWith_(self, A.empty(), _ => true, (s, z) => A.append_(s, z));
}
/**
 * Summarize a sink by running an effect when the sink starts and again when it completes
 */

export 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))));
}
/**
 * Summarize a sink by running an effect when the sink starts and again when it completes
 * @ets_data_first summarized_
 */

export function summarized(summary, f) {
  return self => summarized_(self, summary, f);
}
export function orElse_(self, that) {
  return new Sink(C.orElse_(self.channel, that.channel));
}
/**
 * @ets_data_first orElse_
 */

export function orElse(that) {
  return self => orElse_(self, that);
}
export function zip_(self, that) {
  return zipWith_(self, that, (a, b) => Tp.tuple(a, b));
}
/**
 * @ets_data_first zip_
 */

export function zip(that) {
  return self => zip_(self, that);
}
/**
 * Like `zip`, but keeps only the result from the `that` sink.
 */

export function zipLeft_(self, that) {
  return zipWith_(self, that, (z, _) => z);
}
/**
 * Like `zip`, but keeps only the result from `that sink.
 *
 * @ets_data_first zipLeft_
 */

export function zipLeft(that) {
  return self => zipLeft_(self, that);
} // 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.
 */

export function zipRight_(self, that) {
  return zipWith_(self, that, (_, z1) => z1);
}
/**
 * Like `zip`, but keeps only the result from this sink.
 *
 * @ets_data_first zipRight_
 */

export function zipRight(that) {
  return self => zipRight_(self, that);
}
/**
 * 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`.
 */

export function zipWith_(self, that, f) {
  return chain_(self, z => map_(that, _ => f(z, _)));
}
/**
 * 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_
 */

export function zipWith(that, f) {
  return self => zipWith_(self, that, f);
}
export function exposeLeftover(self) {
  return new Sink(C.map_(C.doneCollect(self.channel), ({
    tuple: [chunks, z]
  }) => Tp.tuple(z, A.flatten(chunks))));
}
export function dropLeftover(self) {
  return new Sink(C.drain(self.channel));
} // TODO: untilOutputEffect_ -> Not implemented

export function accessSink(f) {
  return new Sink(C.unwrap(T.access(_ => f(_).channel)));
}
/**
 * A sink that collects all of its inputs into a chunk.
 */

export function collectAll() {
  return new Sink(collectLoop(A.empty()));
}
/**
 * A sink that collects first `n` elements into a chunk. Note that the chunk
 * is preallocated and must fit in memory.
 */

export function collectAllN(n) {
  return map(_ => _.build())(chain(cb => foldUntil(cb, n, (s, in_) => s.append(in_)))(fromEffect(T.succeedWith(() => A.builder()))));
}
/**
 * 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.
 */

export 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);
    }
  }));
}
/**
 * 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.
 */

export 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_);
    }
  });
}
/**
 * A sink that collects all of its inputs into a set.
 */

export function collectAllToSet() {
  return foldLeftChunks(HS.make(), (acc, as) => A.reduce_(as, acc, (s, a) => HS.add_(s, a)));
}
/**
 * A sink that collects first `n` distinct inputs into a set.
 */

export function collectAllToSetN(n) {
  return foldWeighted(HS.make(), (acc, in_) => HS.has_(acc, in_) ? 0 : 1, n, (s, a) => HS.add_(s, a));
}
/**
 * Accumulates incoming elements into a chunk as long as they verify predicate `p`.
 */

export 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);
    }
  }));
}
/**
 * Accumulates incoming elements into a chunk as long as they verify effectful predicate `p`.
 */

export 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);
    }
  })));
}
/**
 * A sink that counts the number of elements fed to it.
 */

export function count() {
  return foldLeft(0, (s, _) => s + 1);
}
/**
 * Creates a sink halting with the specified `Throwable`.
 */

export function die(e) {
  return failCause(CS.die(e));
}
/**
 * Creates a sink halting with the specified message, wrapped in a
 * `RuntimeException`.
 */

export function dieMessage(message) {
  return failCause(CS.die(new CS.RuntimeError(message)));
}
/**
 * A sink that ignores all of its inputs.
 */

export function drain() {
  const drain = C.readWithCause(_ => drain, C.failCause, _ => C.unit);
  return new Sink(drain);
}
/**
 * Returns a lazily constructed sink that may require effects for its creation.
 */

export function suspend(f) {
  return new Sink(C.suspend(() => f().channel));
}
/**
 * Returns a sink that executes a total effect and ends with its result.
 */

export function succeedWith(effect) {
  return new Sink(C.succeedWith(effect));
}
/**
 * A sink that always fails with the specified error.
 */

export function fail(e) {
  return new Sink(C.fail(e));
}
/**
 * Creates a sink halting with a specified cause.
 */

export function failCause(e) {
  return new Sink(C.failCause(e));
}
/**
 * A sink that folds its inputs with the provided function, termination predicate and initial state.
 */

export 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));
}
/**
 * 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.
 */

export 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));
}
/**
 * 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.
 */

export 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));
}
/**
 * A sink that folds its inputs with the provided function and initial state.
 */

export function foldLeft(z, f) {
  return dropLeftover(fold(z, _ => true, f));
}
/**
 * A sink that folds its input chunks with the provided function and initial state.
 * `f` must preserve chunking-invariance.
 */

export function foldLeftChunks(z, f) {
  return foldChunks(z, _ => true, f);
}
/**
 * A sink that effectfully folds its input chunks with the provided function and initial state.
 * `f` must preserve chunking-invariance.
 */

export function foldLeftChunksEffect(z, f) {
  return dropLeftover(foldChunksEffect(z, _ => true, f));
}
/**
 * A sink that effectfully folds its inputs with the provided function and initial state.
 */

export function foldLeftEffect(z, f) {
  return foldEffect(z, _ => true, f);
}
/**
 * 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.
 */

export 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));
}
/**
 * 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.
 */

export 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))));
}
/**
 * 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.
 */

export function foldWeighted(z, costFn, max, f) {
  return foldWeightedDecompose(z, costFn, max, _ => A.single(_), f);
}
/**
 * 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.
 */

export 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));
}
/**
 * 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.
 */

export 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));
}
/**
 * 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.
 */

export function foldWeightedEffect(z, costFn, max, f) {
  return foldWeightedDecomposeEffect(z, costFn, max, i => T.succeed(A.single(i)), f);
}
/**
 * A sink that effectfully folds its inputs with the provided function, termination predicate and initial state.
 */

export 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));
}
/**
 * A sink that executes the provided effectful function for every element fed to it.
 */

export function forEach(f) {
  return forEachWhile(_ => T.as_(f(_), true));
}
/**
 * A sink that executes the provided effectful function for every chunk fed to it.
 */

export function forEachChunk(f) {
  return forEachChunkWhile(_ => T.as_(f(_), true));
}
/**
 * A sink that executes the provided effectful function for every element fed to it
 * until `f` evaluates to `false`.
 */

export 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);
}
/**
 * A sink that executes the provided effectful function for every chunk fed to it
 * until `f` evaluates to `false`.
 */

export 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);
}
/**
 * Creates a single-value sink produced from an effect
 */

export function fromEffect(b) {
  return new Sink(C.fromEffect(b));
}
/**
 * Creates a sink containing the first value.
 */

export function head() {
  return fold(O.none, O.isNone, (s, in_) => O.fold_(s, () => O.some(in_), _ => s));
}
/**
 * Creates a sink containing the last value.
 */

export function last() {
  return foldLeft(O.none, (_, in_) => O.some(in_));
}
export function leftover(c) {
  return new Sink(C.write(c));
}
export function mkString() {
  return suspend(() => {
    const strings = [];
    return map(_ => strings.join(""))(foldLeftChunks(undefined, (_, els) => A.forEach_(els, el => {
      strings.push(String(el));
    })));
  });
}
export function managed_(resource, fn) {
  return new Sink(C.managed_(resource, _ => fn(_).channel));
}
/**
 *
 * @ets_data_first managed_
 */

export function managed(fn) {
  return resource => managed_(resource, fn);
}
/**
 * A sink that immediately ends with the specified value.
 */

export function succeed(z) {
  return new Sink(C.succeed(z));
}
/**
 * A sink that sums incoming numeric values.
 */

export function sum() {
  return foldLeft(0, (a, b) => a + b);
}
/*
 * A sink that takes the specified number of values.
 */

export 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)));
}
/**
 * A sink that folds its inputs with the provided function, termination predicate and initial state.
 */

export 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));
}
/**
 * A sink that effectfully folds its inputs with the provided function, termination predicate and initial state.
 */

export 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));
}
//# sourceMappingURL=index.js.map