import type { ThreadPullRequestLink } from "@t3tools/contracts"; import type { ThreadPullRequestChain } from "@t3tools/shared/threadPullRequests"; /** One line of a thread's pull-request list: a link plus how deep it sits in its stack. */ export interface PullRequestListLine { readonly link: ThreadPullRequestLink; /** 0 for a pull request on the base branch; each layer above steps in by one. */ readonly depth: number; /** Which chain the line belongs to, so callers can tell one stack's lines from another's. */ readonly chainKey: string; /** Set on the bottom layer of a multi-layer stack, so that row can name the whole stack. */ readonly stack: { readonly kind: ThreadPullRequestChain["kind"]; readonly size: number } | null; } function activityAt(link: ThreadPullRequestLink): number { const ms = Date.parse(link.snapshot?.updatedAt ?? link.linkedAt); return Number.isNaN(ms) ? 0 : ms; } function chainKeyOf(chain: ThreadPullRequestChain): string { const bottom = chain.layers[0]!; return `${bottom.host}/${bottom.repository}#${bottom.number}`; } /** * Flattens chains into indented lines, newest first. A stack sorts by its most recent layer and * then reads bottom to top beneath that slot, so the layer you would review first is at the * bottom of the indent and a fresh push anywhere in the stack floats the whole stack up. */ export function pullRequestListLines( chains: ReadonlyArray, ): ReadonlyArray { const ordered = [...chains].sort( (left, right) => Math.max(...right.layers.map(activityAt)) - Math.max(...left.layers.map(activityAt)), ); return ordered.flatMap((chain) => { const chainKey = chainKeyOf(chain); return chain.layers.map((link, depth) => ({ link, depth, chainKey, stack: depth === 0 && chain.layers.length > 1 ? { kind: chain.kind, size: chain.layers.length } : null, })); }); }