// tracing: off
import "../../../Operator"
import * as Cause from "../../../Cause"
import * as Chunk from "../../../Collections/Immutable/Chunk"
import * as T from "../../../Effect"
import { identity, pipe } from "../../../Function"
import * as M from "../../../Managed"
import * as O from "../../../Option"
import * as C from "../Channel"
import * as Sink from "../Sink"
export const StreamTypeId = /*#__PURE__*/ Symbol()
/**
 * A `Stream<R, E, A>` is a description of a program that, when evaluated,
 * may emit 0 or more values of type `A`, may fail with errors of type `E`
 * and uses an environment of type `R`.
 * One way to think of `Stream` is as a `Effect` program that could emit multiple values.
 *
 * `Stream` is a purely functional *pull* based stream. Pull based streams offer
 * inherent laziness and backpressure, relieving users of the need to manage buffers
 * between operators. As an optimization, `Stream` does not emit single values, but
 * rather an array of values. This allows the cost of effect evaluation to be
 * amortized.
 *
 * `Stream` forms a monad on its `A` type parameter, and has error management
 * facilities for its `E` type parameter, modeled similarly to `Effect` (with some
 * adjustments for the multiple-valued nature of `Stream`). These aspects allow
 * for rich and expressive composition of streams.
 */

export class Stream {
  constructor(channel) {
    this.channel = channel
    this._typeId = StreamTypeId
  }
}
T._R, T._E, T._A
/**
 * Empty stream
 */

export const empty = /*#__PURE__*/ fromChunk(/*#__PURE__*/ Chunk.empty())
/**
 * Returns a stream made of the concatenation in strict order of all the streams
 * produced by passing each element of this stream to `f`
 */

export function chain_(self, f) {
  return new Stream(
    C.concatMap_(self.channel, (o) =>
      Chunk.reduce_(
        Chunk.map_(o, (x) => f(x).channel),
        C.unit,
        (s, a) => C.chain_(s, () => a)
      )
    )
  )
}
/**
 * Returns a stream made of the concatenation in strict order of all the streams
 * produced by passing each element of this stream to `f`
 *
 * @dataFirst chain_
 */

export function chain(f) {
  return (self) => chain_(self, f)
}
/**
 * Combines the chunks from this stream and the specified stream by repeatedly applying the
 * function `f` to extract a chunk using both sides and conceptually "offer"
 * it to the destination stream. `f` can maintain some internal state to control
 * the combining process, with the initial state being specified by `s`.
 */

export function combineChunks_(self, that, s, f) {
  return unwrapManaged(
    M.map_(
      M.bind_(
        M.bind_(M.do, "pullLeft", () => toPull(self)),
        "pullRight",
        () => toPull(that)
      ),
      ({ pullLeft, pullRight }) =>
        unfoldChunksM(s, (s) =>
          T.chain_(f(s, pullLeft, pullRight), (ex) => T.optional(T.done(ex)))
        )
    )
  )
}
/**
 * Combines the chunks from this stream and the specified stream by repeatedly applying the
 * function `f` to extract a chunk using both sides and conceptually "offer"
 * it to the destination stream. `f` can maintain some internal state to control
 * the combining process, with the initial state being specified by `s`.
 *
 * @dataFirst combineChunks_
 */

export function combineChunks(that, s, f) {
  return (self) => combineChunks_(self, that, s, f)
}
/**
 * Halt a stream with the specified exception
 */

export function die(u) {
  return new Stream(C.die(u))
}
/**
 * Halt a stream with the specified exception
 */

export function dieWith(u) {
  return new Stream(C.dieWith(u))
}
/**
 * Halt a stream with the specified error
 */

export function fail(error) {
  return new Stream(C.fail(error))
}
/**
 * Halt a stream with the specified error
 */

export function failWith(error) {
  return new Stream(C.failWith(error))
}
/**
 * Repeats this stream forever.
 */

export function forever(self) {
  return new Stream(C.repeated(self.channel))
}
/**
 * Creates a stream from a `Chunk` of values
 *
 * @param c a chunk of values
 * @return a finite stream of values
 */

export function fromChunk(c) {
  return new Stream(C.unwrap(T.succeedWith(() => C.write(c))))
}
/**
 * Creates a stream from a `Chunk` of values
 *
 * @param c a chunk of values
 * @return a finite stream of values
 */

export function fromChunkWith(c) {
  return new Stream(C.unwrap(T.succeedWith(() => C.writeWith(c))))
}
/**
 * Creates a stream from an effect producing a value of type `A`
 */

export function effect(self) {
  return new Stream(C.unwrap(T.fold_(self, C.fail, (x) => C.write(Chunk.single(x)))))
}
/**
 * Creates a stream from an effect producing a value of type `A` or an empty Stream
 */

export function effectOption(self) {
  return new Stream(
    C.unwrap(
      T.fold_(
        self,
        O.fold(() => C.unit, C.fail),
        (x) => C.write(Chunk.single(x))
      )
    )
  )
}
/**
 * Creates a single-valued stream from a managed resource
 */

export function managed(self) {
  return new Stream(C.managedOut(M.map_(self, Chunk.single)))
}
/**
 * Maps over elements of the stream with the specified effectful function.
 */

export function mapM_(self, f) {
  return loopOnPartialChunksElements_(self, (a, emit) => T.chain_(f(a), emit))
}
/**
 * Maps over elements of the stream with the specified effectful function.
 *
 * @dataFirst mapM_
 */

export function mapM(f) {
  return (self) => mapM_(self, f)
}
/**
 * Flattens this stream-of-streams into a stream made of the concatenation in
 * strict order of all the streams.
 */

export function flatten(self) {
  return chain_(self, identity)
}
/**
 * Loops over the stream chunks concatenating the result of f
 */

export function loopOnChunks_(self, f) {
  const loop = C.readWithCause(
    (chunk) => C.chain_(f(chunk), (cont) => (cont ? loop : C.end(false))),
    C.halt,
    (_) => C.end(false)
  )
  return new Stream(self.channel[">>>"](loop))
}
/**
 * Loops on chunks emitting partially
 */

export function loopOnPartialChunks_(self, f) {
  return loopOnChunks_(self, (chunk) =>
    C.unwrap(
      T.suspend(() => {
        let outputChunk = Chunk.empty()
        return T.catchAll_(
          T.map_(
            f(chunk, (a) =>
              T.succeedWith(() => {
                outputChunk = Chunk.append_(outputChunk, a)
              })
            ),
            (cont) => C.chain_(C.write(outputChunk), () => C.end(cont))
          ),
          (failure) =>
            T.succeedWith(() => {
              if (Chunk.isEmpty(outputChunk)) {
                return C.fail(failure)
              } else {
                return C.chain_(C.write(outputChunk), () => C.fail(failure))
              }
            })
        )
      })
    )
  )
}
/**
 * Loops on chunks elements emitting partially
 */

export function loopOnPartialChunksElements_(self, f) {
  return loopOnPartialChunks_(self, (a, emit) =>
    T.as_(
      Chunk.mapM_(a, (a) => f(a, emit)),
      true
    )
  )
}
/**
 * Transforms the elements of this stream using the supplied function.
 */

export function map_(self, f) {
  return new Stream(C.mapOut_(self.channel, (o) => Chunk.map_(o, f)))
}
/**
 * Transforms the elements of this stream using the supplied function.
 *
 * @dataFirst map_
 */

export function map(f) {
  return (self) => map_(self, f)
}
/**
 * Runs the sink on the stream to produce either the sink's result or an error.
 */

export function run_(self, sink) {
  return C.runDrain(self.channel[">>>"](sink.channel))
}
/**
 * Runs the sink on the stream to produce either the sink's result or an error.
 *
 * @dataFirst run_
 */

export function run(sink) {
  return (self) => run_(self, sink)
}
/**
 * Runs the stream and collects all of its elements to a chunk.
 */

export function runCollect(self) {
  return run_(self, Sink.collectAll())
}
/**
 * Runs the stream and collects ignore its elements.
 */

export function runDrain(self) {
  return run_(self, Sink.drain())
}
/**
 * Creates a single-valued pure stream
 */

export function succeed(o) {
  return fromChunk(Chunk.single(o))
}
/**
 * Creates a single-valued pure stream
 */

export function succeedWith(o) {
  return fromChunkWith(() => Chunk.single(o()))
}

function takeLoop(n) {
  return C.readWithCause(
    (i) => {
      const taken = Chunk.take_(i, n)
      const left = Math.max(n - Chunk.size(taken), 0)

      if (left > 0) {
        return C.chain_(C.write(taken), () => takeLoop(left))
      } else {
        return C.write(taken)
      }
    },
    C.halt,
    C.end
  )
}
/**
 * Takes the specified number of elements from this stream.
 */

export function take_(self, n) {
  if (n <= 0) {
    return empty
  }

  if (!Number.isInteger(n)) {
    return die(new Cause.IllegalArgumentException(`${n} should be an integer`))
  }

  return new Stream(self.channel[">>>"](takeLoop(n)))
}
/**
 * Takes the specified number of elements from this stream.
 *
 * @dataFirst take_
 */

export function take(n) {
  return (self) => take_(self, n)
}
/**
 * Interpret the stream as a managed pull
 */

export function toPull(self) {
  return M.map_(C.toPull(self.channel), (pull) =>
    T.mapError_(pull, (e) => (e._tag === "Left" ? O.some(e.left) : O.none))
  )
}

function unfoldChunksLoop(s, f) {
  return C.unwrap(
    T.map_(
      f(s),
      O.fold(
        () => C.unit,
        ({ tuple: [as, s] }) => C.chain_(C.write(as), () => unfoldChunksLoop(s, f))
      )
    )
  )
}
/**
 * Creates a stream by effectfully peeling off the "layers" of a value of type `S`
 */

export function unfoldChunksM(s, f) {
  return new Stream(unfoldChunksLoop(s, f))
}
/**
 * Creates a stream produced from an effect
 */

export function unwrap(self) {
  return flatten(effect(self))
}
/**
 * Creates a stream produced from a managed
 */

export function unwrapManaged(self) {
  return flatten(managed(self))
}
//# sourceMappingURL=core.js.map
