docs: @SuppressWarnings("WeakerAccess")
      @UiThread
      public void setBackstack(@NonNull List<RouterTransaction> newBackstack, @Nullable ControllerChangeHandler changeHandler) {
          ThreadUtils.ensureMainThread();
  
          List<RouterTransaction> oldTransactions = getBackstack();
          List<RouterTransaction> oldVisibleTransactions = getVisibleTransactions(backstack.iterator());
  
          removeAllExceptVisibleAndUnowned();
          ensureOrderedTransactionIndices(newBackstack);
          ensureNoDuplicateControllers(newBackstack);
  
          backstack.setBackstack(newBackstack);
  
          List<RouterTransaction> transactionsToBeRemoved = new ArrayList<>();
          for (RouterTransaction oldTransaction : oldTransactions) {
              boolean contains = false;
              for (RouterTransaction newTransaction : newBackstack) {
                  if (oldTransaction.controller == newTransaction.controller) {
                      contains = true;
                      break;
                  }
              }
  
              if (!contains) {
                  // Inform the controller that it will be destroyed soon
                  oldTransaction.controller.isBeingDestroyed = true;
                  transactionsToBeRemoved.add(oldTransaction);
              }
          }
  
          // Ensure all new controllers have a valid router set
          Iterator<RouterTransaction> backstackIterator = backstack.reverseIterator();
          while (backstackIterator.hasNext()) {
              RouterTransaction transaction = backstackIterator.next();
              transaction.onAttachedToRouter();
              setControllerRouter(transaction.controller);
          }
  
          if (newBackstack.size() > 0) {
              List<RouterTransaction> reverseNewBackstack = new ArrayList<>(newBackstack);
              Collections.reverse(reverseNewBackstack);
              List<RouterTransaction> newVisibleTransactions = getVisibleTransactions(reverseNewBackstack.iterator());
              boolean newRootRequiresPush = !(newVisibleTransactions.size() > 0 && oldTransactions.contains(newVisibleTransactions.get(0)));
  
              boolean visibleTransactionsChanged = !backstacksAreEqual(newVisibleTransactions, oldVisibleTransactions);
              if (visibleTransactionsChanged) {
                  RouterTransaction oldRootTransaction = oldVisibleTransactions.size() > 0 ? oldVisibleTransactions.get(0) : null;
                  RouterTransaction newRootTransaction = newVisibleTransactions.get(0);
  
                  // Replace the old root with the new one
                  if (oldRootTransaction == null || oldRootTransaction.controller != newRootTransaction.controller) {
                      // Ensure the existing root controller is fully pushed to the view hierarchy
                      if (oldRootTransaction != null) {
                          ControllerChangeHandler.completeHandlerImmediately(oldRootTransaction.controller.getInstanceId());
                      }
                      performControllerChange(newRootTransaction, oldRootTransaction, newRootRequiresPush, changeHandler);
                  }
  
                  // Remove all visible controllers that were previously on the backstack
                  for (int i = oldVisibleTransactions.size() - 1; i > 0; i--) {
                      RouterTransaction transaction = oldVisibleTransactions.get(i);
                      if (!newVisibleTransactions.contains(transaction)) {
                          ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
                          localHandler.setForceRemoveViewOnPush(true);
                          ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId());
                          performControllerChange(null, transaction, newRootRequiresPush, localHandler);
                      }
                  }
  
                  // Add any new controllers to the backstack
                  for (int i = 1; i < newVisibleTransactions.size(); i++) {
                      RouterTransaction transaction = newVisibleTransactions.get(i);
                      if (!oldVisibleTransactions.contains(transaction)) {
                          performControllerChange(transaction, newVisibleTransactions.get(i - 1), true, transaction.pushChangeHandler());
                      }
                  }
              }
  
          } else {
              // Remove all visible controllers that were previously on the backstack
              for (int i = oldVisibleTransactions.size() - 1; i >= 0; i--) {
                  RouterTransaction transaction = oldVisibleTransactions.get(i);
                  ControllerChangeHandler localHandler = changeHandler != null ? changeHandler.copy() : new SimpleSwapChangeHandler();
                  ControllerChangeHandler.completeHandlerImmediately(transaction.controller.getInstanceId());
                  performControllerChange(null, transaction, false, localHandler);
              }
          }
  
          // Destroy all old controllers that are no longer on the backstack. We don't do this when we initially
          // set the backstack to prevent the possibility that they'll be destroyed before the controller
          // change handler runs.
          for (RouterTransaction removedTransaction : transactionsToBeRemoved) {
  
              // Still need to ensure the controller isn't queued up to be removed later on.
              boolean willBeRemoved = false;
              for (ChangeTransaction pendingTransaction : pendingControllerChanges) {
                  if (pendingTransaction.from == removedTransaction.controller) {
                      willBeRemoved = true;
                  }
              }
  
              if (!willBeRemoved) {
                  removedTransaction.controller.destroy();
              }
          }
      }