1import type { FileDiffMetadata } from "@pierre/diffs";
2import type { PullRequestDiffSide } from "@t3tools/contracts";
3
4/**
5 * Whether a conversation's line is really in this file's hunks.
6 *
7 * A thread naming a file is not the same as a thread the diff can show: its line may have moved
8 * out of the change, or sit in a hunk the host withheld. Pinning it anyway would put the remark
9 * against whatever code now occupies that line number, and silently dropping it would lose the
10 * conversation, so the answer decides which of the two lists it belongs in.
11 */
12export function isLineInFileDiff(
13 file: FileDiffMetadata,
14 side: PullRequestDiffSide,
15 line: number,
16): boolean {
17 return file.hunks.some((hunk) =>
18 side === "left"
19 ? line >= hunk.deletionStart && line < hunk.deletionStart + hunk.deletionCount
20 : line >= hunk.additionStart && line < hunk.additionStart + hunk.additionCount,
21 );
22}
23
24/** What the toolbar last asked of every file at once, null being the reader asking nothing yet. */
25export type DiffFoldOverride = "expanded" | "folded" | null;
26
27/**
28 * Whether a file is drawn folded.
29 *
30 * A diff arrives a slice at a time, so the reader's own choices are kept as the difference from
31 * what the toolbar last said rather than as the set of folded files: a file that has not loaded
32 * yet cannot be in a set, and would otherwise land expanded moments after the reader folded
33 * everything. The caller supplies the saved default until the toolbar overrides it; individual
34 * files can still be toggled independently.
35 */
36export function isFileDiffCollapsed(
37 fileKey: string,
38 foldOverride: DiffFoldOverride,
39 toggledFileKeys: ReadonlySet<string>,
40): boolean {
41 const foldedByDefault = foldOverride === "folded";
42 return toggledFileKeys.has(fileKey) ? !foldedByDefault : foldedByDefault;
43}
44
45/**
46 * The reader's fold choices after a file was ticked off, or put back.
47 *
48 * Clearing a file puts it away and un-clearing brings it back, so the tick moves the fold as if
49 * the reader had pressed the chevron themselves, which keeps folding a difference from what the
50 * toolbar last asked, and so keeps "collapse all" from ticking anything off.
51 */
52export function toggleFileDiffFoldForViewed(
53 fileKey: string,
54 viewed: boolean,
55 foldOverride: DiffFoldOverride,
56 toggledFileKeys: ReadonlySet<string>,
57): ReadonlySet<string> {
58 if (isFileDiffCollapsed(fileKey, foldOverride, toggledFileKeys) === viewed)
59 return toggledFileKeys;
60 const next = new Set(toggledFileKeys);
61 if (next.has(fileKey)) next.delete(fileKey);
62 else next.add(fileKey);
63 return next;
64}