recursivelyLoadBlocksFromService function

Future<List<Block>> recursivelyLoadBlocksFromService(
  1. BlocksService blocksService,
  2. String blockId, {
  3. int? maxDepth,
  4. int concurrency = 4,
  5. BlockChildrenCache? cache,
})

Variant that accepts a BlocksService directly (useful for testing).

Implementation

Future<List<Block>> recursivelyLoadBlocksFromService(
  BlocksService blocksService,
  String blockId, {
  int? maxDepth,
  int concurrency = 4,
  BlockChildrenCache? cache,
}) async {
  final childrenCache = cache ?? BlockChildrenCache();

  // Use BFS across depth levels to better control concurrency per level.
  final all = <Block>[];
  final processedParents = <String>{};

  // Queue of (parentId, depth)
  var currentLevel = <_QueueItem>[_QueueItem(parentId: blockId, depth: 0)];

  while (currentLevel.isNotEmpty) {
    // If maxDepth is set and the current level exceeds it, stop.
    final levelDepth = currentLevel.first.depth;
    if (maxDepth != null && levelDepth > maxDepth) {
      break;
    }

    // Fetch children for all parents in this level with bounded concurrency.
    final nextLevel = <_QueueItem>[];

    for (var i = 0; i < currentLevel.length; i += concurrency) {
      final batch = currentLevel.sublist(
        i,
        (i + concurrency > currentLevel.length)
            ? currentLevel.length
            : i + concurrency,
      );

      final futures = batch.map((item) async {
        // Avoid fetching same parent's children multiple times.
        if (processedParents.contains(item.parentId)) {
          return <Block>[];
        }
        processedParents.add(item.parentId);

        final children = await _getAllChildren(
          blocksService,
          item.parentId,
          childrenCache,
        );

        // Collect children for this level
        all.addAll(children);

        // If we can go deeper, schedule children that have further descendants.
        final nextDepth = item.depth + 1;
        if (maxDepth == null || nextDepth <= maxDepth) {
          for (final child in children) {
            if (child.hasChildren) {
              nextLevel.add(_QueueItem(parentId: child.id, depth: nextDepth));
            }
          }
        }

        return children;
      }).toList();

      // Wait for this batch to complete before starting the next batch.
      await Future.wait(futures);
    }

    currentLevel = nextLevel;
  }

  return all;
}