import * as tracing_1 from "../../../Tracing";
const fileName_1 = "(@effect-ts/system): _src/Experimental/Stream/_internal/SortedByKey.ts";
import * as A from "../../../Collections/Immutable/Chunk";
import * as Tp from "../../../Collections/Immutable/Tuple";
import * as T from "../../../Effect";
import * as Ex from "../../../Exit";
import { identity } from "../../../Function";
import * as O from "../../../Option";
import { combineChunks_ } from "./core";

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

}

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

}

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

}

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

}

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

}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Uses the functions `left`, `right`, and `both`
 * to handle the cases where a key and value exist in this stream, that
 * stream, or both streams.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * The execution strategy `exec` will be used to determine whether to pull
 * from the streams sequentially or in parallel.
 */


export function zipAllSortedByKeyWithExec_(self, that, left, right, both, comparator, exec) {
  const pull = (state, pullLeft, pullRight) => {
    switch (state._tag) {
      case "DrainLeft":
        return T.fold_(pullLeft, e => Ex.fail(e), leftChunk => Ex.succeed(Tp.tuple(A.map_(leftChunk, ({
          tuple: [k, a]
        }) => Tp.tuple(k, left(a))), new DrainLeft())), fileName_1 + ":80:23");

      case "DrainRight":
        return T.fold_(pullRight, e => Ex.fail(e), rightChunk => Ex.succeed(Tp.tuple(A.map_(rightChunk, ({
          tuple: [k, b]
        }) => Tp.tuple(k, right(b))), new DrainRight())), fileName_1 + ":92:23");

      case "PullBoth":
        {
          switch (exec._tag) {
            case "Sequential":
              return T.foldM_(pullLeft, O.fold(() => pull(new DrainRight(), pullLeft, pullRight), e => T.succeed(Ex.fail(O.some(e)), fileName_1 + ":110:33")), leftChunk => A.isEmpty(leftChunk) ? pull(new PullBoth(), pullLeft, pullRight) : pull(new PullRight(leftChunk), pullLeft, pullRight), fileName_1 + ":106:28");

            default:
              return T.foldM_(T.zipPar_(T.unsome(pullLeft), T.unsome(pullRight), fileName_1 + ":119:24"), e => T.succeed(Ex.fail(O.some(e)), fileName_1 + ":120:31"), ({
                tuple: [a, b]
              }) => {
                if (O.isSome(a) && O.isSome(b)) {
                  const leftChunk = a.value;
                  const rightChunk = b.value;

                  if (A.isEmpty(leftChunk) && A.isEmpty(rightChunk)) {
                    return pull(new PullBoth(), pullLeft, pullRight);
                  } else if (A.isEmpty(leftChunk)) {
                    return pull(new PullLeft(rightChunk), pullLeft, pullRight);
                  } else if (A.isEmpty(rightChunk)) {
                    return pull(new PullRight(leftChunk), pullLeft, pullRight);
                  } else {
                    return T.succeed(Ex.succeed(mergeSortedByKeyChunk(leftChunk, rightChunk)), fileName_1 + ":133:37");
                  }
                } else if (O.isSome(a)) {
                  const leftChunk = a.value;
                  return A.isEmpty(leftChunk) ? pull(new DrainLeft(), pullLeft, pullRight) : T.succeed(Ex.succeed(Tp.tuple(A.map_(leftChunk, ({
                    tuple: [k, a]
                  }) => Tp.tuple(k, left(a))), new DrainLeft())), fileName_1 + ":142:32");
                } else if (O.isSome(b)) {
                  const rightChunk = b.value;
                  return A.isEmpty(rightChunk) ? pull(new DrainLeft(), pullLeft, pullRight) : T.succeed(Ex.succeed(Tp.tuple(A.map_(rightChunk, ({
                    tuple: [k, b]
                  }) => Tp.tuple(k, right(b))), new DrainRight())), fileName_1 + ":157:32");
                } else {
                  return T.succeed(Ex.fail(O.none), fileName_1 + ":168:35");
                }
              }, fileName_1 + ":118:28");
          }
        }

      case "PullLeft":
        {
          const rightChunk = state.rightChunk;
          return T.foldM_(pullLeft, O.fold(() => T.succeed(Ex.succeed(Tp.tuple(A.map_(rightChunk, ({
            tuple: [k, b]
          }) => Tp.tuple(k, right(b))), new DrainRight())), fileName_1 + ":185:24"), e => T.succeed(Ex.fail(O.some(e)), fileName_1 + ":193:29")), leftChunk => A.isEmpty(leftChunk) ? pull(new PullLeft(rightChunk), pullLeft, pullRight) : T.succeed(Ex.succeed(mergeSortedByKeyChunk(leftChunk, rightChunk)), fileName_1 + ":198:26"), fileName_1 + ":177:24");
        }

      case "PullRight":
        {
          const leftChunk = state.leftChunk;
          return T.foldM_(pullRight, O.fold(() => T.succeed(Ex.succeed(Tp.tuple(A.map_(leftChunk, ({
            tuple: [k, a]
          }) => Tp.tuple(k, left(a))), new DrainLeft())), fileName_1 + ":212:24"), e => T.succeed(Ex.fail(O.some(e)), fileName_1 + ":220:29")), rightChunk => A.isEmpty(rightChunk) ? pull(new PullRight(leftChunk), pullLeft, pullRight) : T.succeed(Ex.succeed(mergeSortedByKeyChunk(leftChunk, rightChunk)), fileName_1 + ":225:26"), fileName_1 + ":204:24");
        }
    }
  };

  const mergeSortedByKeyChunk = (leftChunk, rightChunk) => {
    const builder = A.builder();
    let state;
    let leftIndex = 0;
    let rightIndex = 0;
    let leftTuple = A.unsafeGet_(leftChunk, leftIndex);
    let rightTuple = A.unsafeGet_(rightChunk, rightIndex);
    let k1 = leftTuple.get(0);
    let a = leftTuple.get(1);
    let k2 = rightTuple.get(0);
    let b = rightTuple.get(1);
    let loop = true;

    const hasNext = (c, index) => index < A.size(c) - 1;

    while (loop) {
      const compare = comparator(k1, k2);

      if (compare === 0) {
        builder.append(Tp.tuple(k1, both(a, b)));

        if (hasNext(leftChunk, leftIndex) && hasNext(rightChunk, rightIndex)) {
          leftIndex += 1;
          rightIndex += 1;
          leftTuple = A.unsafeGet_(leftChunk, leftIndex);
          rightTuple = A.unsafeGet_(rightChunk, rightIndex);
          k1 = leftTuple.get(0);
          a = leftTuple.get(1);
          k2 = rightTuple.get(0);
          b = rightTuple.get(1);
        } else if (hasNext(leftChunk, leftIndex)) {
          state = new PullRight(A.drop_(leftChunk, leftIndex + 1));
          loop = false;
        } else if (hasNext(rightChunk, rightIndex)) {
          state = new PullLeft(A.drop_(rightChunk, rightIndex + 1));
          loop = false;
        } else {
          state = new PullBoth();
          loop = false;
        }
      } else if (compare < 0) {
        builder.append(Tp.tuple(k1, left(a)));

        if (hasNext(leftChunk, leftIndex)) {
          leftIndex += 1;
          leftTuple = A.unsafeGet_(leftChunk, leftIndex);
          k1 = leftTuple.get(0);
          a = leftTuple.get(1);
        } else {
          const rightBuilder = A.builder();
          rightBuilder.append(rightTuple);

          while (hasNext(rightChunk, rightIndex)) {
            rightIndex += 1;
            rightTuple = A.unsafeGet_(rightChunk, rightIndex);
            rightBuilder.append(rightTuple);
            state = new PullLeft(rightBuilder.build());
            loop = false;
          }
        }
      } else {
        builder.append(Tp.tuple(k2, right(b)));

        if (hasNext(rightChunk, rightIndex)) {
          rightIndex += 1;
          rightTuple = A.unsafeGet_(rightChunk, rightIndex);
          k2 = rightTuple.get(0);
          b = rightTuple.get(1);
        } else {
          const leftBuilder = A.builder();
          leftBuilder.append(leftTuple);

          while (hasNext(leftChunk, leftIndex)) {
            leftIndex += 1;
            leftTuple = A.unsafeGet_(leftChunk, leftIndex);
            leftBuilder.append(leftTuple);
            state = new PullRight(leftBuilder.build());
            loop = false;
          }
        }
      }
    }

    return Tp.tuple(builder.build(), state);
  };

  return combineChunks_(self, that, new PullBoth(), pull);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Uses the functions `left`, `right`, and `both`
 * to handle the cases where a key and value exist in this stream, that
 * stream, or both streams.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * The execution strategy `exec` will be used to determine whether to pull
 * from the streams sequentially or in parallel.
 *
 * @ets_data_first zipAllSortedByKeyWithExec_
 */

export function zipAllSortedByKeyWithExec(that, left, right, both, comparator, exec) {
  return self => zipAllSortedByKeyWithExec_(self, that, left, right, both, comparator, exec);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Uses the functions `left`, `right`, and `both`
 * to handle the cases where a key and value exist in this stream, that
 * stream, or both streams.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 */

export function zipAllSortedByKeyWith_(self, that, left, right, both, comparator) {
  return zipAllSortedByKeyWithExec_(self, that, left, right, both, comparator, T.parallel);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Uses the functions `left`, `right`, and `both`
 * to handle the cases where a key and value exist in this stream, that
 * stream, or both streams.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * @ets_data_first zipAllSortedByKeyWith_
 */

export function zipAllSortedByKeyWith(that, left, right, both, comparator) {
  return self => zipAllSortedByKeyWith_(self, that, left, right, both, comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Keeps only values from that stream, using the
 * specified value `default` to fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 */

export function zipAllSortedByKeyRight_(self, that, default_, comparator) {
  return zipAllSortedByKeyWith_(self, that, _ => default_, identity, (_, b) => b, comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Keeps only values from that stream, using the
 * specified value `default` to fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * @ets_data_first zipAllSortedByKeyRight_
 */

export function zipAllSortedByKeyRight(that, default_, comparator) {
  return self => zipAllSortedByKeyRight_(self, that, default_, comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Keeps only values from this stream, using the
 * specified value `default` to fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 */

export function zipAllSortedByKeyLeft_(self, that, default_, comparator) {
  return zipAllSortedByKeyWith_(self, that, identity, _ => default_, (a, _) => a, comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Keeps only values from this stream, using the
 * specified value `default` to fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * @ets_data_first zipAllSortedByKeyLeft_
 */

export function zipAllSortedByKeyLeft(that, default_, comparator) {
  return self => zipAllSortedByKeyLeft_(self, that, default_, comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Combines values associated with each key into a
 * tuple, using the specified values `defaultLeft` and `defaultRight` to
 * fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 */

export function zipAllSortedByKey_(self, that, defaultLeft, defaultRight, comparator) {
  return zipAllSortedByKeyWith_(self, that, _ => Tp.tuple(_, defaultRight), _ => Tp.tuple(defaultLeft, _), (a, b) => Tp.tuple(a, b), comparator);
}
/**
 * Zips this stream that is sorted by distinct keys and the specified
 * stream that is sorted by distinct keys to produce a new stream that is
 * sorted by distinct keys. Combines values associated with each key into a
 * tuple, using the specified values `defaultLeft` and `defaultRight` to
 * fill in missing values.
 *
 * This allows zipping potentially unbounded streams of data by key in
 * constant space but the caller is responsible for ensuring that the
 * streams are sorted by distinct keys.
 *
 * @ets_data_first zipAllSortedByKey_
 */

export function zipAllSortedByKey(that, defaultLeft, defaultRight, comparator) {
  return self => zipAllSortedByKey_(self, that, defaultLeft, defaultRight, comparator);
}
//# sourceMappingURL=SortedByKey.js.map