File size: 1,295 Bytes
8523e75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import type { Issue } from "@paperclipai/shared";

export interface IssueTree {
  roots: Issue[];
  childMap: Map<string, Issue[]>;
}

/**
 * Builds a parent→children tree from a flat list of issues.
 *
 * - `roots` contains issues whose parent is absent from the list (or have no
 *   parent at all), so orphaned sub-tasks are always visible at root level.
 * - `childMap` maps each parent id to its direct children in list order.
 */
export function buildIssueTree(items: Issue[]): IssueTree {
  const itemIds = new Set(items.map((i) => i.id));
  const roots = items.filter((i) => !i.parentId || !itemIds.has(i.parentId));
  const childMap = new Map<string, Issue[]>();
  for (const item of items) {
    if (item.parentId && itemIds.has(item.parentId)) {
      const arr = childMap.get(item.parentId) ?? [];
      arr.push(item);
      childMap.set(item.parentId, arr);
    }
  }
  return { roots, childMap };
}

/**
 * Returns the total number of descendants (all depths) of `id` in `childMap`.
 * Used to accurately label collapsed parent badges like "(3 sub-tasks)".
 */
export function countDescendants(id: string, childMap: Map<string, Issue[]>): number {
  const children = childMap.get(id) ?? [];
  return children.reduce((sum, c) => sum + 1 + countDescendants(c.id, childMap), 0);
}