public static <T> void depthFirstTraverseTreeWithPath(
    @NotNull T root,
    @NotNull BiFunction<? super List<? extends T>, T, Collection<? extends T>> action
  ) {
    List<T> treePath = new ArrayList<>();
    depthFirstTraverseTree(new Pair<>(0, root), node -> {
      T oldParent = treePath.isEmpty() ? null : treePath.get(treePath.size() - 1);
      while (oldParent != null && treePath.size() > node.first) {
        treePath.remove(treePath.size() - 1);
        oldParent = treePath.isEmpty() ? null : treePath.get(treePath.size() - 1);
      }

      Collection<? extends T> children = action.apply(treePath, node.second);

      treePath.add(node.second);

      List<Pair<Integer, T>> childrenWithDepth = new ArrayList<>();
      for (T child : children) {
        childrenWithDepth.add(new Pair<>(node.first + 1, child));
      }
      return childrenWithDepth;
    });
  }
public static <T> void depthFirstTraverseTreeWithPath(
    @NotNull T root,
    @NotNull BiFunction<? super List<? extends T>, T, Collection<? extends T>> action
  )