private Set<PathFinder.Path> explore(
            Shape shape,
            List<Relationship> path,
            Set<Shape> visited,
            NeighborProvider provider
    ) {
        if (visited.contains(shape)) {
            return Collections.singleton(new PathFinder.Path(path));
        }

        Set<Shape> newVisited = new LinkedHashSet<>(visited);
        newVisited.add(shape);

        // Sort edges alphabetically by shape to make the order predictable.
        Map<Shape, Relationship> shapeRelationshipMap = new TreeMap<>();
        for (Relationship rel : provider.getNeighbors(shape)) {
            if (rel.getRelationshipType().getDirection() == RelationshipDirection.DIRECTED) {
                if (!rel.getNeighborShapeId().equals(shape.getId()) && rel.getNeighborShape().isPresent()) {
                    shapeRelationshipMap.put(rel.getNeighborShape().get(), rel);
                }
            }
        }

        if (shapeRelationshipMap.isEmpty()) {
            shapes.add(shape);
            return Collections.emptySet();
        }

        Set<PathFinder.Path> recursivePaths = new LinkedHashSet<>();
        for (Map.Entry<Shape, Relationship> entry : shapeRelationshipMap.entrySet()) {
            List<Relationship> newPath = new ArrayList<>(path.size() + 1);
            newPath.addAll(path);
            newPath.add(entry.getValue());
            recursivePaths.addAll(explore(entry.getKey(), newPath, newVisited, provider));
        }

        if (recursivePaths.isEmpty()) {
            shapes.add(shape);
        }

        return recursivePaths;
    }
Creates a reverse-topological ordering of shapes.

<p>This kind of reverse topological ordering is useful for languages
like C++ that need to define shapes before they can be referenced.
Only non-recursive shapes are reverse-topologically ordered using
{@link #getOrderedShapes()}. However, recursive shapes are queryable
through {@link #getRecursiveShapes()}. When this returned {@code Set} is
iterated, recursive shapes are ordered by their degree of recursion (the
number of edges across all recursive closures), and then by shape ID
when multiple shapes have the same degree of recursion.

<p>The recursion closures of a shape can be queried using
{@link #getRecursiveClosure(ToShapeId)}. This method returns a list of
paths from the shape back to itself. This list can be useful for code
generation to generate different code based on if a recursive path
passes through particular types of shapes.
smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/TopologicalIndex.java