"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.filterInputM = exports.filterInputM_ = exports.filterInput = exports.filterInput_ = exports.filter = exports.filter_ = exports.collectAllWhileM = exports.collectAllWhile = exports.collectAllToMapN = exports.collectAllN = exports.foldWeighted = exports.foldWeightedDecomposeM = exports.foldWeightedDecompose = exports.foldUntilM = exports.foldUntil = exports.foldLeftM = exports.foldM = exports.foldLeft = exports.fold = exports.dropWhileM = exports.dropWhile = exports.branchAfter = exports.prepend = exports.last = exports.head = exports.fromFunctionM = exports.fromFunction = exports.fromEffect = exports.fromPush = exports.identity = exports.halt = exports.die = exports.fail = exports.mapError = exports.mapError_ = exports.mapM = exports.mapM_ = exports.mapChunksM = exports.mapChunksM_ = exports.mapChunks = exports.mapChunks_ = exports.map = exports.map_ = exports.then = exports.transducer = exports.Transducer = void 0;
// ets_tracing: off
require("../../Operator");
const Chunk = __importStar(require("../../Collections/Immutable/Chunk"));
const Map = __importStar(require("../../Collections/Immutable/Map"));
const Tp = __importStar(require("../../Collections/Immutable/Tuple"));
const Ex = __importStar(require("../../Exit"));
const Function_1 = require("../../Function");
const O = __importStar(require("../../Option"));
const RM = __importStar(require("../../RefM"));
const T = __importStar(require("../_internal/effect"));
const M = __importStar(require("../_internal/managed"));
const R = __importStar(require("../_internal/ref"));
// Contract notes for transducers:
// - When a None is received, the transducer must flush all of its internal state
//   and remain empty until subsequent Some(Chunk) values.
//
//   Stated differently, after a first push(None), all subsequent push(None) must
//   result in empty [].
class Transducer {
    constructor(push) {
        this.push = push;
    }
}
exports.Transducer = Transducer;
/**
 * Contract notes for transducers:
 * - When a None is received, the transducer must flush all of its internal state
 *   and remain empty until subsequent Some(Chunk) values.
 *
 *   Stated differently, after a first push(None), all subsequent push(None) must
 *   result in empty [].
 */
const transducer = (push) => new Transducer(push);
exports.transducer = transducer;
/**
 * Compose this transducer with another transducer, resulting in a composite transducer.
 */
const then = (that) => (self) => (0, exports.transducer)((M.zipWith_(self.push, that.push, (pushLeft, pushRight) => O.fold(() => (T.chain_(pushLeft(O.none), (cl) => Chunk.isEmpty(cl)
    ? pushRight(O.none)
    : (T.zipWith_(pushRight(O.some(cl)), pushRight(O.none), Chunk.concat_)))), (inputs) => (T.chain_(pushLeft(O.some(inputs)), (cl) => pushRight(O.some(cl))))))));
exports.then = then;
/**
 * Transforms the outputs of this transducer.
 */
function map_(fa, f) {
    return new Transducer(M.map_(fa.push, (push) => (input) => T.map_(push(input), Chunk.map(f))));
}
exports.map_ = map_;
/**
 * Transforms the outputs of this transducer.
 */
function map(f) {
    return (fa) => map_(fa, f);
}
exports.map = map;
/**
 * Transforms the chunks emitted by this transducer.
 */
function mapChunks_(fa, f) {
    return new Transducer(M.map_(fa.push, (push) => (input) => T.map_(push(input), f)));
}
exports.mapChunks_ = mapChunks_;
/**
 * Transforms the chunks emitted by this transducer.
 */
function mapChunks(f) {
    return (fa) => mapChunks_(fa, f);
}
exports.mapChunks = mapChunks;
/**
 * Effectfully transforms the chunks emitted by this transducer.
 */
function mapChunksM_(fa, f) {
    return new Transducer(M.map_(fa.push, (push) => (input) => T.chain_(push(input), f)));
}
exports.mapChunksM_ = mapChunksM_;
/**
 * Effectfully transforms the chunks emitted by this transducer.
 */
function mapChunksM(f) {
    return (fa) => mapChunksM_(fa, f);
}
exports.mapChunksM = mapChunksM;
/**
 * Effectually transforms the outputs of this transducer
 */
function mapM_(fa, f) {
    return new Transducer(M.map_(fa.push, (push) => (input) => T.chain_(push(input), Chunk.mapM(f))));
}
exports.mapM_ = mapM_;
/**
 * Effectually transforms the outputs of this transducer
 */
function mapM(f) {
    return (fa) => mapM_(fa, f);
}
exports.mapM = mapM;
/**
 * Transforms the errors of this transducer.
 */
function mapError_(pab, f) {
    return new Transducer(M.map_(pab.push, (push) => (is) => T.mapError_(push(is), f)));
}
exports.mapError_ = mapError_;
/**
 * Transforms the errors of this transducer.
 */
function mapError(f) {
    return (pab) => mapError_(pab, f);
}
exports.mapError = mapError;
/**
 * Creates a transducer that always fails with the specified failure.
 */
function fail(e) {
    return new Transducer(M.succeed((_) => T.fail(e)));
}
exports.fail = fail;
/**
 * Creates a transducer that always dies with the specified exception.
 */
function die(error) {
    return new Transducer(M.succeed((_) => T.die(error)));
}
exports.die = die;
/**
 * Creates a transducer that always fails with the specified cause.
 */
function halt(c) {
    return new Transducer(M.succeed((_) => T.halt(c)));
}
exports.halt = halt;
/**
 * The identity transducer. Passes elements through.
 */
function identity() {
    return fromPush(O.fold(() => T.succeed(Chunk.empty()), T.succeed));
}
exports.identity = identity;
/**
 * Creates a transducer from a chunk processing function.
 */
function fromPush(push) {
    return new Transducer(M.succeed(push));
}
exports.fromPush = fromPush;
/**
 * Creates a transducer that always evaluates the specified effect.
 */
function fromEffect(task) {
    return new Transducer(M.succeed((_) => T.map_(task, Chunk.single)));
}
exports.fromEffect = fromEffect;
/**
 * Creates a transducer that purely transforms incoming values.
 */
function fromFunction(f) {
    return map_(identity(), f);
}
exports.fromFunction = fromFunction;
/**
 * Creates a transducer that effectfully transforms incoming values.
 */
function fromFunctionM(f) {
    return mapM_(identity(), f);
}
exports.fromFunctionM = fromFunctionM;
/**
 * Creates a transducer that returns the first element of the stream, if it exists.
 */
function head() {
    return foldLeft(O.none, (acc, o) => O.fold_(acc, () => O.some(o), () => acc));
}
exports.head = head;
/**
 * Creates a transducer that returns the last element of the stream, if it exists.
 */
function last() {
    return foldLeft(O.none, (_, o) => O.some(o));
}
exports.last = last;
/**
 * Emits the provided chunk before emitting any other value.
 */
function prepend(values) {
    return new Transducer(M.map_(R.makeManagedRef(values), (state) => (is) => O.fold_(is, () => R.getAndSet_(state, Chunk.empty()), (os) => (T.map_(R.getAndSet_(state, Chunk.empty()), (c) => (Chunk.isEmpty(c) ? os : Chunk.concat_(c, os)))))));
}
exports.prepend = prepend;
/**
 * Reads the first n values from the stream and uses them to choose the transducer that will be used for the remainder of the stream.
 * If the stream ends before it has collected n values the partial chunk will be provided to f.
 */
function branchAfter(n, f) {
    const initialState = {
        _tag: "Collecting",
        data: Chunk.empty()
    };
    const toCollect = Math.max(0, n);
    return new Transducer(M.chain_(M.scope, (allocate) => M.map_(RM.makeManagedRefM(initialState), (state) => (is) => O.fold_(is, () => (T.chain_(RM.getAndSet_(state, initialState), (s) => {
        switch (s._tag) {
            case "Collecting": {
                return M.use_(f(s.data).push, (f) => f(O.none));
            }
            case "Emitting": {
                return T.zipLeft_(s.push(O.none), s.finalizer(Ex.unit));
            }
        }
    })), (data) => RM.modify_(state, (s) => {
        switch (s._tag) {
            case "Emitting": {
                return T.map_(s.push(O.some(data)), (_) => Tp.tuple(_, s));
            }
            case "Collecting": {
                if (Chunk.isEmpty(data)) {
                    return T.succeed(Tp.tuple(Chunk.empty(), s));
                }
                else {
                    const remaining = toCollect - Chunk.size(s.data);
                    if (remaining <= Chunk.size(data)) {
                        const { tuple: [newCollected, remainder] } = Chunk.splitAt_(data, remaining);
                        return T.chain_(allocate(f(Chunk.concat_(s.data, newCollected)).push), ({ tuple: [finalizer, push] }) => T.map_(push(O.some(remainder)), (_) => Tp.tuple(_, {
                            _tag: "Emitting",
                            finalizer,
                            push
                        })));
                    }
                    else {
                        return T.succeed(Tp.tuple(Chunk.empty(), {
                            _tag: "Collecting",
                            data: Chunk.concat_(s.data, data)
                        }));
                    }
                }
            }
        }
    })))));
}
exports.branchAfter = branchAfter;
/**
 * Creates a transducer that starts consuming values as soon as one fails
 * the predicate `p`.
 */
function dropWhile(predicate) {
    return new Transducer(M.map_(R.makeManagedRef(true), (dropping) => (is) => O.fold_(is, () => T.succeed(Chunk.empty()), (is) => R.modify_(dropping, (b) => {
        switch (b) {
            case true: {
                const is1 = Chunk.dropWhile_(is, predicate);
                return Tp.tuple(is1, Chunk.isEmpty(is1));
            }
            case false: {
                return Tp.tuple(is, false);
            }
        }
    }))));
}
exports.dropWhile = dropWhile;
/**
 * Creates a transducer that starts consuming values as soon as one fails
 * the effectful predicate `p`.
 */
function dropWhileM(p) {
    return new Transducer((M.map_(M.let_(M.bind_(M.do, "dropping", () => R.makeManagedRef(true)), "push", ({ dropping }) => (is) => O.fold_(is, () => T.succeed(Chunk.empty()), (is) => (T.chain_(T.chain_(dropping.get, (b) => b
        ? T.map_(Chunk.dropWhileM_(is, p), (l) => [l, Chunk.isEmpty(l)])
        : T.succeed([is, false])), ([is, pt]) => T.as_(dropping.set(pt), is))))), ({ push }) => push)));
}
exports.dropWhileM = dropWhileM;
function foldGo(in_, state, progress, initial, contFn, f) {
    return Chunk.reduce_(in_, [Chunk.empty(), state, progress], ([os0, state, _], i) => {
        const o = f(state, i);
        if (contFn(o)) {
            return [os0, o, true];
        }
        else {
            return [Chunk.append_(os0, o), initial, false];
        }
    });
}
/**
 * Creates a transducer by folding over a structure of type `O` for as long as
 * `contFn` results in `true`. The transducer will emit a value when `contFn`
 * evaluates to `false` and then restart the folding.
 */
function fold(initial, contFn, f) {
    return new Transducer(M.map_(R.makeManagedRef(O.some(initial)), (state) => (is) => O.fold_(is, () => (T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), Chunk.single))), (in_) => R.modify_(state, (s) => {
        const [o, s2, progress] = foldGo(in_, O.getOrElse_(s, () => initial), O.isSome(s), initial, contFn, f);
        if (progress) {
            return Tp.tuple(o, O.some(s2));
        }
        else {
            return Tp.tuple(o, O.none);
        }
    }))));
}
exports.fold = fold;
/**
 * Creates a transducer by folding over a structure of type `O`. The transducer will
 * fold the inputs until the stream ends, resulting in a stream with one element.
 */
function foldLeft(initial, f) {
    return fold(initial, () => true, f);
}
exports.foldLeft = foldLeft;
/**
 * Creates a sink by effectfully folding over a structure of type `S`.
 */
function foldM(initial, contFn, f) {
    const init = O.some(initial);
    const go = (in_, state, progress) => Chunk.reduce_(in_, T.succeed([Chunk.empty(), state, progress]), (b, i) => T.chain_(b, ([os0, state, _]) => T.map_(f(state, i), (o) => {
        if (contFn(o)) {
            return [os0, o, true];
        }
        else {
            return [Chunk.append_(os0, o), initial, false];
        }
    })));
    return new Transducer(M.map_(R.makeManagedRef(init), (state) => (is) => O.fold_(is, () => (T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), Chunk.single))), (in_) => (T.chain_(T.chain_(state.get, (s) => go(in_, O.getOrElse_(s, () => initial), O.isSome(s))), ([os, s, progress]) => progress
        ? T.zipRight_(state.set(O.some(s)), T.succeed(os))
        : T.zipRight_(state.set(O.none), T.succeed(os)))))));
}
exports.foldM = foldM;
/**
 * Creates a transducer by effectfully folding over a structure of type `O`. The transducer will
 * fold the inputs until the stream ends, resulting in a stream with one element.
 */
function foldLeftM(initial, f) {
    return foldM(initial, () => true, f);
}
exports.foldLeftM = foldLeftM;
/**
 * Creates a transducer that folds elements of type `I` into a structure
 * of type `O` until `max` elements have been folded.
 *
 * Like `foldWeighted`, but with a constant cost function of 1.
 */
function foldUntil(initial, max, f) {
    return (map((t) => t[0])(fold((0, Function_1.tuple)(initial, 0), ([_, n]) => n < max, ([o, count], i) => [f(o, i), count + 1])));
}
exports.foldUntil = foldUntil;
/**
 * Creates a transducer that effectfully folds elements of type `I` into a structure
 * of type `O` until `max` elements have been folded.
 *
 * Like `foldWeightedM`, but with a constant cost function of 1.
 */
function foldUntilM(initial, max, f) {
    return (map((t) => t[0])(foldM((0, Function_1.tuple)(initial, 0), ([_, n]) => n < max, ([o, count], i) => T.map_(f(o, i), (o) => [o, count + 1]))));
}
exports.foldUntilM = foldUntilM;
/**
 * Creates a transducer that folds elements of type `I` into a structure
 * of type `O`, until `max` worth of elements (determined by the `costFn`)
 * have been folded.
 *
 * The `decompose` function will be used for decomposing elements that
 * cause an `O` 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 transducer
 * to fail.
 */
function foldWeightedDecompose(initial, costFn, max, decompose, f) {
    const initialState = {
        result: initial,
        cost: 0
    };
    const go = (in_, os0, state, dirty) => Chunk.reduce_(in_, [os0, state, dirty], ([os0, state, _], i) => {
        const total = state.cost + costFn(state.result, i);
        if (total > max) {
            const is = decompose(i);
            if (Chunk.size(is) <= 1 && !dirty) {
                return [
                    Chunk.append_(os0, f(state.result, !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i)),
                    initialState,
                    false
                ];
            }
            else if (Chunk.size(is) <= 1 && dirty) {
                const elem = !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i;
                return [
                    Chunk.append_(os0, state.result),
                    {
                        result: f(initialState.result, elem),
                        cost: costFn(initialState.result, elem)
                    },
                    true
                ];
            }
            else {
                return go(is, os0, state, dirty);
            }
        }
        else {
            return [os0, { result: f(state.result, i), cost: total }, true];
        }
    });
    return new Transducer(M.map_(R.makeManagedRef(O.some(initialState)), (state) => (is) => O.fold_(is, () => (T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), (s) => Chunk.single(s.result)))), (in_) => R.modify_(state, (s) => {
        const [o, s2, dirty] = go(in_, Chunk.empty(), O.getOrElse_(s, () => initialState), O.isSome(s));
        if (dirty) {
            return Tp.tuple(o, O.some(s2));
        }
        else {
            return Tp.tuple(o, O.none);
        }
    }))));
}
exports.foldWeightedDecompose = foldWeightedDecompose;
/**
 * Creates a transducer that effectfully folds elements of type `I` 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 foldWeightedDecomposeM(initial, costFn, max, decompose, f) {
    const initialState = {
        result: initial,
        cost: 0
    };
    const go = (in_, os, state, dirty) => Chunk.reduce_(in_, T.succeed([os, state, dirty]), (o, i) => T.chain_(o, ([os, state, _]) => T.chain_(costFn(state.result, i), (cost) => {
        const total = cost + state.cost;
        if (total > max) {
            return T.chain_(decompose(i), (is) => {
                if (Chunk.size(is) <= 1 && !dirty) {
                    return T.map_(f(state.result, !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i), (o) => [Chunk.append_(os, o), initialState, false]);
                }
                else if (Chunk.size(is) <= 1 && dirty) {
                    const elem = !Chunk.isEmpty(is) ? Chunk.unsafeGet_(is, 0) : i;
                    return T.zipWith_(f(initialState.result, elem), costFn(initialState.result, elem), (result, cost) => [
                        Chunk.append_(os, state.result),
                        { result, cost },
                        true
                    ]);
                }
                else {
                    return go(is, os, state, dirty);
                }
            });
        }
        else {
            return T.map_(f(state.result, i), (o) => [os, { result: o, cost: total }, true]);
        }
    })));
    return new Transducer(M.map_(R.makeManagedRef(O.some(initialState)), (state) => (is) => O.fold_(is, () => (T.map_(R.getAndSet_(state, O.none), O.fold(() => Chunk.empty(), (s) => Chunk.single(s.result)))), (in_) => (T.chain_(T.chain_(state.get, (s) => go(in_, Chunk.empty(), O.getOrElse_(s, () => initialState), O.isSome(s))), ([os, s, dirty]) => dirty
        ? T.zipRight_(state.set(O.some(s)), T.succeed(os))
        : T.zipRight_(state.set(O.none), T.succeed(os)))))));
}
exports.foldWeightedDecomposeM = foldWeightedDecomposeM;
/**
 * Creates a transducer that folds elements of type `I` into a structure
 * of type `O`, 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 transducer to cross the `max` cost. See `foldWeightedDecompose`
 * for a variant that can handle these cases.
 */
function foldWeighted(initial, costFn, max, f) {
    return foldWeightedDecompose(initial, costFn, max, Chunk.single, f);
}
exports.foldWeighted = foldWeighted;
function collectAllNGo(n, in_, leftover, acc) {
    // eslint-disable-next-line no-constant-condition
    while (1) {
        const { tuple: [left, nextIn] } = Chunk.splitAt_(in_, n - Chunk.size(leftover));
        if (Chunk.size(leftover) + Chunk.size(left) < n)
            return Tp.tuple(acc, Chunk.concat_(leftover, left));
        else {
            const nextOut = !Chunk.isEmpty(leftover)
                ? Chunk.append_(acc, Chunk.concat_(leftover, left))
                : Chunk.append_(acc, left);
            in_ = nextIn;
            leftover = Chunk.empty();
            acc = nextOut;
        }
    }
    throw new Error("Bug");
}
/**
 * Creates a transducer accumulating incoming values into chunks of maximum size `n`.
 */
function collectAllN(n) {
    return new Transducer(M.map_(R.makeManagedRef(Chunk.empty()), (state) => (is) => O.fold_(is, () => T.map_(R.getAndSet_(state, Chunk.empty()), (leftover) => !Chunk.isEmpty(leftover) ? Chunk.single(leftover) : Chunk.empty()), (in_) => R.modify_(state, (leftover) => collectAllNGo(n, in_, leftover, Chunk.empty())))));
}
exports.collectAllN = collectAllN;
/**
 * Creates a transducer accumulating incoming values into maps of up to `n` keys. Elements
 * are mapped to keys using the function `key`; elements mapped to the same key will
 * be merged with the function `f`.
 */
function collectAllToMapN(n, key, merge) {
    return (filter((0, Function_1.not)(Map.isEmpty))(foldWeighted(Map.empty, (acc, i) => (acc.has(key(i)) ? 0 : 1), n, (acc, i) => {
        const k = key(i);
        if (acc.has(k))
            return Map.insert(k, merge(acc.get(k), i))(acc);
        else
            return Map.insert(k, i)(acc);
    })));
}
exports.collectAllToMapN = collectAllToMapN;
/**
 * Accumulates incoming elements into a chunk as long as they verify predicate `p`.
 */
function collectAllWhile(p) {
    return (filter((x) => !Chunk.isEmpty(x))(map((t) => t[0])(fold([Chunk.empty(), true], (t) => t[1], ([is, _], i) => (p(i) ? [Chunk.append_(is, i), true] : [is, false])))));
}
exports.collectAllWhile = collectAllWhile;
/**
 * Accumulates incoming elements into a chunk as long as they verify effectful predicate `p`.
 */
function collectAllWhileM(p) {
    return (filter((x) => !Chunk.isEmpty(x))(map((t) => t[0])(foldM([Chunk.empty(), true], (t) => t[1], ([is, _], i) => T.map_(p(i), (b) => (b ? [Chunk.append_(is, i), true] : [is, false]))))));
}
exports.collectAllWhileM = collectAllWhileM;
function filter_(fa, predicate) {
    return new Transducer(M.map_(fa.push, (push) => (is) => T.map_(push(is), Chunk.filter(predicate))));
}
exports.filter_ = filter_;
function filter(predicate) {
    return (fa) => filter_(fa, predicate);
}
exports.filter = filter;
function filterInput_(fa, predicate) {
    return new Transducer(M.map_(fa.push, (push) => (is) => push(O.map_(is, Chunk.filter(predicate)))));
}
exports.filterInput_ = filterInput_;
function filterInput(predicate) {
    return (fa) => filterInput_(fa, predicate);
}
exports.filterInput = filterInput;
/**
 * Effectually filters the inputs of this transducer.
 */
function filterInputM_(fa, predicate) {
    return new Transducer(M.map_(fa.push, (push) => (is) => O.fold_(is, () => push(O.none), (x) => (T.chain_(Chunk.filterM_(x, predicate), (in_) => push(O.some(in_)))))));
}
exports.filterInputM_ = filterInputM_;
/**
 * Effectually filters the inputs of this transducer.
 */
function filterInputM(predicate) {
    return (fa) => filterInputM_(fa, predicate);
}
exports.filterInputM = filterInputM;
//# sourceMappingURL=index.js.map