// ets_tracing: off
import "../../Operator";
import * as C from "../../Cause";
import { currentTime } from "../../Clock";
import * as A from "../../Collections/Immutable/Chunk";
import * as List from "../../Collections/Immutable/List";
import * as Tp from "../../Collections/Immutable/Tuple";
import * as E from "../../Either";
import * as Ex from "../../Exit/api";
import { identity, pipe } from "../../Function";
import * as H from "../../Hub";
import * as L from "../../Layer";
import * as O from "../../Option";
import * as Q from "../../Queue";
import { matchTag } from "../../Utils";
import * as T from "../_internal/effect";
import * as F from "../_internal/fiber";
import * as M from "../_internal/managed";
import * as R from "../_internal/ref";
import * as Push from "../Push";
import { transducer } from "../Transducer"; // Important notes while writing sinks and combinators:
// - What return values for sinks mean:
//   Effect.unit - "need more values"
//   Effect.fail([Either.Right(z), l]) - "ended with z and emit leftover l"
//   Effect.fail([Either.Left(e), l]) - "failed with e and emit leftover l"
// - Result of processing of the stream using the sink must not depend on how the stream is chunked
//   (chunking-invariance)
//   pipe(stream, run(sink), Effect.either) === pipe(stream, chunkN(1), run(sink), Effect.either)
// - Sinks should always end when receiving a `None`. It is a defect to not end with some
//   sort of result (even a failure) when receiving a `None`.
// - Sinks can assume they will not be pushed again after emitting a value.

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

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

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(z) {
  return p => f => self => new Sink(M.chain_(R.makeManagedRef(z), acc => {
    return M.map_(Push.restartable(self.push), ({
      tuple: [push, restart]
    }) => {
      const go = (s, in_, end) => T.catchAll_(T.as_(push(in_), s), ({
        tuple: [e, leftover]
      }) => E.fold_(e, e => Push.fail(e, leftover), z => {
        if (p(z)) {
          const s1 = f(s, z);

          if (A.isEmpty(leftover)) {
            if (end) {
              return Push.emit(s1, A.empty());
            } else {
              return T.as_(restart, s1);
            }
          } else {
            return T.zipRight_(restart, go(s1, O.some(leftover), end));
          }
        } else {
          return Push.emit(s, leftover);
        }
      }));

      return in_ => T.chain_(acc.get, s => T.chain_(go(s, in_, O.isNone(in_)), s1 => acc.set(s1)));
    });
  }));
}
/**
 * Transforms this sink's input elements.
 */

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

export function contramap(f) {
  return self => contramap_(self, f);
}
/**
 * Effectfully transforms this sink's input elements.
 */

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

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

export function contramapChunks_(self, f) {
  return new Sink(M.map_(self.push, push => input => push(O.map_(input, f))));
}
/**
 * Transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */

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

export function contramapChunksM_(self, f) {
  return new Sink(M.map_(self.push, push => {
    return input => O.fold_(input, () => push(O.none), value => T.chain_(T.mapError_(f(value), e => Tp.tuple(E.left(e), A.empty())), is => push(O.some(is))));
  }));
}
/**
 * Effectfully transforms this sink's input chunks.
 * `f` must preserve chunking-invariance
 */

export function contramapChunksM(f) {
  return self => contramapChunksM_(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.
 */

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

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

export function dimapM(f, g) {
  return self => dimapM_(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.
 */

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 dimapChunksM_(self, f, g) {
  return mapM_(contramapChunksM_(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 dimapChunksM(f, g) {
  return self => dimapChunksM_(self, f, g);
}
/**
 * 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 foldM_(self, e => fail(e)(), 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.
 */

export function chain(f) {
  return self => chain_(self, f);
}
/**
 * Recovers from errors by accepting one effect to execute for the case of an
 * error, and one effect to execute for the case of success.
 *
 * This method has better performance than `either` since no intermediate
 * value is allocated and does not require subsequent calls to `flatMap` to
 * define the next effect.
 *
 * The error parameter of the returned `IO` may be chosen arbitrarily, since
 * it will depend on the `IO`s returned by the given continuations.
 */

export function foldM_(self, failure, success) {
  return new Sink(M.map_(M.bind_(M.bind_(M.bind_(M.bind_(M.do, "switched", () => T.toManaged(R.makeRef(false))), "thisPush", () => self.push), "thatPush", () => T.toManaged(R.makeRef(_ => T.unit))), "openThatPush", () => M.switchable()), ({
    openThatPush,
    switched,
    thatPush,
    thisPush
  }) => {
    return in_ => T.chain_(switched.get, sw => {
      if (!sw) {
        return T.catchAll_(thisPush(in_), v => {
          const leftover = v[1];
          const nextSink = E.fold_(v[0], failure, success);
          return T.chain_(T.tap_(openThatPush(nextSink.push), thatPush.set), p => T.zipRight_(switched.set(true), O.fold_(in_, () => T.zipRight_(T.when_(p(O.some(leftover)), () => !A.isEmpty(leftover)), p(O.none)), () => T.when_(p(O.some(leftover)), () => !A.isEmpty(leftover)))));
        });
      } else {
        return T.chain_(thatPush.get, p => p(in_));
      }
    });
  }));
}
/**
 * Recovers from errors by accepting one effect to execute for the case of an
 * error, and one effect to execute for the case of success.
 *
 * This method has better performance than `either` since no intermediate
 * value is allocated and does not require subsequent calls to `flatMap` to
 * define the next effect.
 *
 * The error parameter of the returned `IO` may be chosen arbitrarily, since
 * it will depend on the `IO`s returned by the given continuations.
 */

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

export function map_(self, f) {
  return new Sink(M.map_(self.push, sink => inputs => T.mapError_(sink(inputs), e => Tp.tuple(E.map_(e.get(0), f), e.get(1)))));
}
/**
 * Transforms this sink's result.
 */

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(M.map_(self.push, p => {
    return in_ => T.mapError_(p(in_), e => Tp.tuple(E.mapLeft_(e.get(0), f), e.get(1)));
  }));
}
/**
 * Transforms the errors emitted by this sink using `f`.
 */

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

export function mapM_(self, f) {
  return new Sink(M.map_(self.push, push => {
    return inputs => T.catchAll_(push(inputs), ({
      tuple: [e, left]
    }) => E.fold_(e, e => Push.fail(e, left), z => T.foldM_(f(z), e => Push.fail(e, left), z2 => Push.emit(z2, left))));
  }));
}
/**
 * Effectfully transforms this sink's result.
 */

export function mapM(f) {
  return self => mapM_(self, f);
}
/**
 * Runs both sinks in parallel on the input, , returning the result or the error from the
 * one that finishes first.
 */

export function race_(self, that) {
  return map_(raceBoth_(self, that), E.merge);
}
/**
 * Runs both sinks in parallel on the input, , returning the result or the error from the
 * one that finishes first.
 */

export function race(that) {
  return self => race_(self, that);
}
/**
 * Runs both sinks in parallel on the input, returning the result or the error from the
 * one that finishes first.
 */

export function raceBoth_(self, that) {
  return new Sink(M.map_(M.bind_(M.bind_(M.do, "p1", () => self.push), "p2", () => that.push), ({
    p1,
    p2
  }) => i => T.raceWith_(p1(i), p2(i), (res1, fib2) => Ex.foldM_(res1, f => T.zipRight_(F.interrupt(fib2), T.halt(C.map(({
    tuple: [r, leftover]
  }) => Tp.tuple(E.map_(r, E.left), leftover))(f))), () => T.mapError_(F.join(fib2), ({
    tuple: [r, leftover]
  }) => Tp.tuple(E.map_(r, E.right), leftover))), (res2, fib1) => Ex.foldM_(res2, f => T.zipRight_(F.interrupt(fib1), T.halt(C.map(({
    tuple: [r, leftover]
  }) => Tp.tuple(E.map_(r, E.right), leftover))(f))), () => T.mapError_(F.join(fib1), ({
    tuple: [r, leftover]
  }) => Tp.tuple(E.map_(r, E.left), leftover))))));
}
/**
 * Runs both sinks in parallel on the input, returning the result or the error from the
 * one that finishes first.
 */

export function raceBoth(that) {
  return self => raceBoth_(self, that);
}
/**
 * Returns the sink that executes this one and times its execution.
 */

export function timed(self) {
  return new Sink(M.zipWith_(self.push, T.toManaged(currentTime), (push, start) => {
    return in_ => T.catchAll_(push(in_), ({
      tuple: [e, leftover]
    }) => E.fold_(e, e => Push.fail(e, leftover), z => T.chain_(currentTime, stop => Push.emit(Tp.tuple(z, stop - start), leftover))));
  }));
}
/**
 * Converts this sink to a transducer that feeds incoming elements to the sink
 * and emits the sink's results as outputs. The sink will be restarted when
 * it ends.
 */

export function toTransducer(self) {
  return transducer(M.map_(Push.restartable(self.push), ({
    tuple: [push, restart]
  }) => {
    const go = input => T.foldM_(push(input), ({
      tuple: [e, leftover]
    }) => E.fold_(e, e => T.fail(e), z => T.zipRight_(restart, A.isEmpty(leftover) || O.isNone(input) ? T.succeed(A.single(z)) : T.map_(go(O.some(leftover)), more => A.prepend_(more, z)))), _ => T.succeed(A.empty()));

    return input => go(input);
  }));
}
/**
 * Feeds inputs to this sink until it yields a result, then switches over to the
 * provided sink until it yields a result, combining the two results in a tuple.
 */

export function zip_(self, that) {
  return zipWith_(self, that, Tp.tuple);
}
/**
 * Feeds inputs to this sink until it yields a result, then switches over to the
 * provided sink until it yields a result, combining the two results in a tuple.
 */

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 the `that` sink.
 */

export function zipLeft(that) {
  return self => zipLeft_(self, that);
}
/**
 * Runs both sinks in parallel on the input and combines the results in a tuple.
 */

export function zipPar_(self, that) {
  return zipWithPar_(self, that, Tp.tuple);
}
/**
 * Runs both sinks in parallel on the input and combines the results in a tuple.
 */

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

export function zipParLeft_(self, that) {
  return zipWithPar_(self, that, (b, _) => b);
}
/**
 * Like `zipPar`, but keeps only the result from this sink.
 */

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

export function zipParRight_(self, that) {
  return zipWithPar_(self, that, (_, c) => c);
}
/**
 * Like `zipPar`, but keeps only the result from the `that` sink.
 */

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

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`.
 */

export function zipWith(that, f) {
  return self => zipWith_(self, that, f);
}

class BothRunning {
  constructor() {
    this._tag = "BothRunning";
  }

}

const bothRunning = /*#__PURE__*/new BothRunning();

class LeftDone {
  constructor(value) {
    this.value = value;
    this._tag = "LeftDone";
  }

}

class RightDone {
  constructor(value) {
    this.value = value;
    this._tag = "RightDone";
  }

}
/**
 * Runs both sinks in parallel on the input and combines the results
 * using the provided function.
 */


export function zipWithPar_(self, that, f) {
  return new Sink(M.map_(M.bind_(M.bind_(M.bind_(M.do, "ref", () => T.toManaged(R.makeRef(bothRunning))), "p1", () => self.push), "p2", () => that.push), ({
    p1,
    p2,
    ref
  }) => {
    return in_ => T.chain_(ref.get, state => {
      const newState = matchTag({
        BothRunning: () => {
          const l = T.foldM_(p1(in_), ({
            tuple: [e, l]
          }) => E.fold_(e, e => Push.fail(e, l), z => T.succeed(O.some(Tp.tuple(z, l)))), _ => T.succeed(O.none));
          const r = T.foldM_(p2(in_), ({
            tuple: [e, l]
          }) => E.fold_(e, e => Push.fail(e, l), z => T.succeed(O.some(Tp.tuple(z, l)))), _ => T.succeed(O.none));
          return T.chain_(T.zipPar_(l, r), ({
            tuple: [lr, rr]
          }) => {
            if (O.isSome(lr)) {
              const [z, l] = lr.value.tuple;

              if (O.isSome(rr)) {
                const [z1, l1] = rr.value.tuple;
                return T.fail(Tp.tuple(E.right(f(z, z1)), A.size(l) > A.size(l1) ? l1 : l));
              } else {
                return T.succeed(new LeftDone(z));
              }
            } else {
              if (O.isSome(rr)) {
                const [z1] = rr.value.tuple;
                return T.succeed(new RightDone(z1));
              } else {
                return T.succeed(bothRunning);
              }
            }
          });
        },
        LeftDone: ({
          value: z
        }) => T.as_(T.catchAll_(p2(in_), ({
          tuple: [e, leftover]
        }) => E.fold_(e, e => Push.fail(e, leftover), z1 => Push.emit(f(z, z1), leftover))), state),
        RightDone: ({
          value: z1
        }) => T.as_(T.catchAll_(p1(in_), ({
          tuple: [e, leftover]
        }) => E.fold_(e, e => Push.fail(e, leftover), z => Push.emit(f(z, z1), leftover))), state)
      })(state);
      return T.chain_(newState, ns => ns === state ? T.unit : ref.set(ns));
    });
  }));
}
/**
 * Runs both sinks in parallel on the input and combines the results
 * using the provided function.
 */

export function zipWithPar(that, f) {
  return self => zipWithPar_(self, that, f);
}
/**
 * Exposes leftover
 */

export function exposeLeftover(self) {
  return new Sink(M.map_(self.push, p => {
    return in_ => T.mapError_(p(in_), ({
      tuple: [v, leftover]
    }) => Tp.tuple(E.map_(v, z => Tp.tuple(z, leftover)), A.empty()));
  }));
}
/**
 * Drops any leftover
 */

export function dropLeftover(self) {
  return new Sink(M.map_(self.push, p => in_ => T.mapError_(p(in_), ({
    tuple: [v, _]
  }) => Tp.tuple(v, A.empty()))));
}

function untilOutputMGo(in_, end, push, restart, f) {
  return T.catchAll_(push(in_), ({
    tuple: [e, leftover]
  }) => E.fold_(e, e => Push.fail(e, leftover), z => T.chain_(T.mapError_(f(z), err => Tp.tuple(E.left(err), leftover)), satisfied => {
    if (satisfied) {
      return Push.emit(O.some(z), leftover);
    } else if (A.isEmpty(leftover)) {
      return end ? Push.emit(O.none, A.empty()) : T.zipRight_(restart, Push.more);
    } else {
      return untilOutputMGo(O.some(leftover), end, push, restart, f);
    }
  })));
}
/**
 * Creates a sink that produces values until one verifies
 * the predicate `f`.
 */


export function untilOutputM_(self, f) {
  return new Sink(M.map_(Push.restartable(self.push), ({
    tuple: [push, restart]
  }) => is => untilOutputMGo(is, O.isNone(is), push, restart, f)));
}
/**
 * Creates a sink that produces values until one verifies
 * the predicate `f`.
 */

export function untilOutputM(f) {
  return self => untilOutputM_(self, f);
}
/**
 * Provides the sink with its required environment, which eliminates
 * its dependency on `R`.
 */

export function provideAll_(self, r) {
  return new Sink(M.map_(M.provideAll_(self.push, r), push => i => T.provideAll_(push(i), r)));
}
/**
 * Provides the sink with its required environment, which eliminates
 * its dependency on `R`.
 */

export function provideAll(r) {
  return self => provideAll_(self, r);
}
/**
 * Provides some of the environment required to run this effect,
 * leaving the remainder `R0`.
 */

export function provideSome_(self, f) {
  return new Sink(M.map_(M.provideSome_(self.push, f), push => i => T.provideSome_(push(i), f)));
}
/**
 * Provides some of the environment required to run this effect,
 * leaving the remainder `R0`.
 */

export function provideSome(f) {
  return self => provideSome_(self, f);
}
/**
 * Provides a layer to the `Managed`, which translates it to another level.
 */

export function provideLayer(layer) {
  return self => provideLayer_(self, layer);
}
/**
 * Provides a layer to the `Managed`, which translates it to another level.
 */

export function provideLayer_(self, layer) {
  return new Sink(M.chain_(L.build(layer), r => M.map_(M.provideAll_(self.push, r), push => i => T.provideAll_(push(i), r))));
}
/**
 * Splits the environment into two parts, providing one part using the
 * specified layer and leaving the remainder `R0`.
 */

export function provideSomeLayer(layer) {
  return self => provideLayer(layer["+++"](L.identity()))(self);
}
/**
 * Creates a Sink from a managed `Push`
 */

export function managedPush(push) {
  return new Sink(push);
}
/**
 * Accesses the environment of the sink in the context of a sink.
 */

export function accessM(f) {
  return new Sink(M.chain_(M.environment(), env => f(env).push));
}
/**
 * A sink that collects all of its inputs into an array.
 */

export function collectAll() {
  return reduceLeftChunks(A.empty())((s, i) => A.concat_(s, i));
}
/**
 * A sink that collects all of its inputs into an list.
 */

export function collectAllToList() {
  return reduceLeftChunks(List.empty())((s, i) => List.concat_(s, List.from(i)));
}
/**
 * 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) {
  return f => new Sink(M.suspend(() => reduceLeftChunks(new Map())((acc, as) => A.reduce_(as, acc, (acc, a) => {
    const k = key(a);
    const v = acc.get(k);
    return acc.set(k, v ? f(v, a) : a);
  })).push));
}
/**
 * A sink that collects all of its inputs into a set.
 */

export function collectAllToSet() {
  return map_(collectAll(), as => new Set(as));
}
/**
 * A sink that counts the number of elements fed to it.
 */

export const count = /*#__PURE__*/reduceLeft(0)((s, _) => s + 1);
/**
 * Creates a sink halting with the specified `Throwable`.
 */

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

export function dieMessage(m) {
  return halt(C.die(new C.RuntimeError(m)));
}
/**
 * A sink that ignores its inputs.
 */

export const drain = /*#__PURE__*/dropLeftover( /*#__PURE__*/forEach(_ => T.unit));
/**
 * A sink that always fails with the specified error.
 */

export function fail(e) {
  return () => fromPush(c => {
    const leftover = O.fold_(c, () => A.empty(), x => x);
    return Push.fail(e, leftover);
  });
}

const reduceChunkGo = (s, chunk, idx, len, contFn, f) => {
  if (idx === len) {
    return [s, O.none];
  } else {
    const s1 = f(s, A.unsafeGet_(chunk, idx));

    if (contFn(s1)) {
      return reduceChunkGo(s1, chunk, idx + 1, len, contFn, f);
    } else {
      return [s1, O.some(A.drop_(chunk, idx + 1))];
    }
  }
};
/**
 * A sink that folds its inputs with the provided function, termination predicate and initial state.
 */


export function reduce(z, contFn, f) {
  if (contFn(z)) {
    return new Sink(M.map_(M.bind_(M.do, "state", () => T.toManaged(R.makeRef(z))), ({
      state
    }) => is => O.fold_(is, () => T.chain_(state.get, s => Push.emit(s, A.empty())), is => T.chain_(state.get, s => {
      const [st, l] = reduceChunkGo(s, is, 0, A.size(is), contFn, f);
      return O.fold_(l, () => T.zipRight_(state.set(st), Push.more), leftover => Push.emit(st, leftover));
    }))));
  } else {
    return succeed(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 reduceChunks(z) {
  return contFn => f => reduceChunksM(z)(contFn)((z, i) => T.succeed(f(z, i)));
}
/**
 * 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 reduceChunksM(z) {
  return contFn => f => {
    if (contFn(z)) {
      return new Sink(M.map_(M.bind_(M.do, "state", () => T.toManaged(R.makeRef(z))), ({
        state
      }) => {
        return is => O.fold_(is, () => T.chain_(state.get, s => Push.emit(s, A.empty())), is => T.chain_(T.mapError_(T.chain_(state.get, _ => f(_, is)), e => Tp.tuple(E.left(e), A.empty())), s => {
          if (contFn(s)) {
            return T.zipRight_(state.set(s), Push.more);
          } else {
            return Push.emit(s, A.empty());
          }
        }));
      }));
    } else {
      return succeed(z);
    }
  };
}

function reduceMGo(s, chunk, idx, len, contFn, f) {
  if (idx === len) {
    return T.succeed([s, O.none]);
  } else {
    return T.foldM_(f(s, A.unsafeGet_(chunk, idx)), e => T.fail([e, A.drop_(chunk, idx + 1)]), s1 => contFn(s1) ? reduceMGo(s1, chunk, idx + 1, len, contFn, f) : T.succeed([s1, O.some(A.drop_(chunk, idx + 1))]));
  }
}
/**
 * A sink that effectfully folds its inputs with the provided function, termination predicate and initial state.
 *
 * This sink may terminate in the middle of a chunk and discard the rest of it. See the discussion on the
 * ZSink class scaladoc on sinks vs. transducers.
 */


export function reduceM(z, contFn, f) {
  if (contFn(z)) {
    return new Sink(M.map_(M.bind_(M.do, "state", () => T.toManaged(R.makeRef(z))), ({
      state
    }) => is => O.fold_(is, () => T.chain_(state.get, s => Push.emit(s, A.empty())), is => T.chain_(state.get, s => T.foldM_(reduceMGo(s, is, 0, A.size(is), contFn, f), err => Push.fail(...err), ([st, l]) => O.fold_(l, () => T.zipRight_(state.set(st), Push.more), leftover => Push.emit(st, leftover)))))));
  } else {
    return succeed(z);
  }
}
/**
 * A sink that folds its inputs with the provided function and initial state.
 */

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

export function reduceLeftChunks(z) {
  return f => dropLeftover(reduceChunks(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 reduceLeftChunksM(z) {
  return f => dropLeftover(reduceChunksM(z)(_ => true)(f));
}
/**
 * A sink that effectfully folds its inputs with the provided function and initial state.
 */

export function reduceLeftM(z) {
  return f => dropLeftover(reduceM(z, _ => true, f));
}

function forEachGo(chunk, idx, len, f) {
  if (idx === len) {
    return Push.more;
  } else {
    return T.foldM_(f(A.unsafeGet_(chunk, idx)), e => Push.fail(e, A.drop_(chunk, idx + 1)), () => forEachGo(chunk, idx + 1, len, f));
  }
}
/**
 * A sink that executes the provided effectful function for every element fed to it.
 */


export function forEach(f) {
  return fromPush(O.fold(() => Push.emit(undefined, A.empty()), is => forEachGo(is, 0, A.size(is), f)));
}
/**
 * A sink that executes the provided effectful function for every chunk fed to it.
 */

export function forEachChunk(f) {
  return fromPush(in_ => O.fold_(in_, () => Push.emit(undefined, A.empty()), is => T.zipRight_(T.mapError_(f(is), e => Tp.tuple(E.left(e), A.empty())), Push.more)));
}
/**
 * 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) => {
    if (idx === len) {
      return Push.more;
    } else {
      return T.foldM_(f(A.unsafeGet_(chunk, idx)), e => Push.fail(e, A.drop_(chunk, idx + 1)), b => {
        if (b) {
          return go(chunk, idx + 1, len);
        } else {
          return Push.emit(undefined, A.drop_(chunk, idx));
        }
      });
    }
  };

  return fromPush(in_ => O.fold_(in_, () => Push.emit(undefined, A.empty()), is => go(is, 0, A.size(is))));
}
/**
 * Creates a single-value sink produced from an effect
 */

export function fromEffect(b) {
  return () => fromPush(in_ => {
    const leftover = O.fold_(in_, () => A.empty(), identity);
    return T.foldM_(b, e => Push.fail(e, leftover), z => Push.emit(z, leftover));
  });
}
/**
 * Creates a sink from a Push
 */

export function fromPush(push) {
  return new Sink(M.succeed(push));
}
/**
 * Creates a sink halting with a specified cause.
 */

export function halt(e) {
  return fromPush(_ => Push.halt(e));
}
/**
 * Creates a sink containing the first value.
 */

export function head() {
  return new Sink(M.succeed(in_ => O.fold_(in_, () => Push.emit(O.none, A.empty()), ch => A.isEmpty(ch) ? Push.more : Push.emit(A.head(ch), A.empty()))));
}
/**
 * Creates a sink containing the last value.
 */

export function last() {
  return new Sink(M.map_(M.bind_(M.do, "state", () => T.toManaged(R.makeRef(O.none))), ({
    state
  }) => is => T.chain_(state.get, last => O.fold_(is, () => Push.emit(last, A.empty()), ch => O.fold_(A.last(ch), () => Push.more, l => T.zipRight_(state.set(O.some(l)), Push.more))))));
}
/**
 * A sink that depends on another managed value
 * `resource` will be finalized after the processing.
 */

export function managed_(resource, fn) {
  return M.chain_(M.fold_(resource, err => fail(err)(), m => fn(m)), _ => _.push);
}
/**
 * A sink that depends on another managed value
 * `resource` will be finalized after the processing.
 */

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

export function succeed(z) {
  return fromPush(c => {
    const leftover = O.fold_(c, () => A.empty(), x => x);
    return Push.emit(z, leftover);
  });
}
/**
 * A sink that sums incoming numeric values.
 */

export const sum = /*#__PURE__*/reduceLeft(0)((a, b) => a + b);
/**
 * A sink that takes the specified number of values.
 */

export function take(n) {
  return new Sink(M.map_(M.bind_(M.do, "state", () => T.toManaged(R.makeRef(A.empty()))), ({
    state
  }) => {
    return is => T.chain_(state.get, take => O.fold_(is, () => {
      if (n >= 0) {
        return Push.emit(take, A.empty());
      } else {
        return Push.emit(A.empty(), take);
      }
    }, ch => {
      const remaining = n - A.size(take);

      if (remaining <= A.size(ch)) {
        const {
          tuple: [chunk, leftover]
        } = A.splitAt_(ch, remaining);
        return T.zipRight_(state.set(A.empty()), Push.emit(A.concat_(take, chunk), leftover));
      } else {
        return T.zipRight_(state.set(A.concat_(take, ch)), Push.more);
      }
    }));
  }));
}
/**
 * A sink with timed execution.
 */

export const timedDrain = /*#__PURE__*/map_( /*#__PURE__*/timed(drain), _ => _.get(1));
/**
 * A sink that executes the provided effectful function for every chunk fed to it.
 */

export function foreachChunk(f) {
  return fromPush(o => O.fold_(o, () => Push.emit(undefined, A.empty()), is => T.zipRight_(T.mapError_(f(is), e => Tp.tuple(E.left(e), A.empty())), Push.more)));
}
/**
 * Create a sink which enqueues each element into the specified queue.
 */

export function fromQueue(queue) {
  return forEachChunk(x => Q.offerAll_(queue, x));
}
/**
 * Create a sink which enqueues each element into the specified queue.
 * The queue will be shutdown once the stream is closed.
 */

export function fromQueueWithShutdown(queue) {
  return new Sink(M.chain_(M.map_(M.make_(T.succeed(queue), Q.shutdown), fromQueue), _ => _.push));
}
/**
 * Create a sink which publishes each element to the specified hub.
 */

export function fromHub(hub) {
  return fromQueue(H.toQueue(hub));
}
/**
 * Create a sink which publishes each element to the specified hub.
 * The hub will be shutdown once the stream is closed.
 */

export function fromHubWithShutdown(hub) {
  return fromQueueWithShutdown(H.toQueue(hub));
}
//# sourceMappingURL=index.js.map