1import { useCallback, useMemo } from "react";
2
3import { updateReviewExpandedFileIds, updateReviewViewedFileIds } from "./reviewState";
4import type { ReviewRenderableFile } from "./reviewModel";
5
6function getDefaultReviewExpandedFileIds(
7 files: ReadonlyArray<ReviewRenderableFile>,
8): ReadonlyArray<string> {
9 return files.map((file) => file.id);
10}
11
12export function getValidReviewFileIds(
13 files: ReadonlyArray<ReviewRenderableFile>,
14 fileIds: ReadonlyArray<string> | undefined,
15): ReadonlyArray<string> {
16 if (fileIds === undefined) {
17 return getDefaultReviewExpandedFileIds(files);
18 }
19
20 const fileIdSet = new Set(files.map((file) => file.id));
21 return fileIds.filter((id) => fileIdSet.has(id));
22}
23
24export function getValidExplicitReviewFileIds(
25 files: ReadonlyArray<ReviewRenderableFile>,
26 fileIds: ReadonlyArray<string> | undefined,
27): ReadonlyArray<string> {
28 if (fileIds === undefined) {
29 return [];
30 }
31
32 return getValidReviewFileIds(files, fileIds);
33}
34
35export function toggleReviewFileId(
36 fileIds: ReadonlyArray<string>,
37 fileId: string,
38): ReadonlyArray<string> {
39 return fileIds.includes(fileId) ? fileIds.filter((id) => id !== fileId) : [...fileIds, fileId];
40}
41
42export function removeReviewFileId(
43 fileIds: ReadonlyArray<string>,
44 fileId: string,
45): ReadonlyArray<string> {
46 return fileIds.includes(fileId) ? fileIds.filter((id) => id !== fileId) : fileIds;
47}
48
49export function useReviewFileVisibility(input: {
50 readonly threadKey: string | null;
51 readonly sectionId: string | null;
52 readonly files: ReadonlyArray<ReviewRenderableFile>;
53 readonly cachedExpandedFileIds: ReadonlyArray<string> | undefined;
54 readonly cachedViewedFileIds: ReadonlyArray<string> | undefined;
55}) {
56 const { cachedExpandedFileIds, cachedViewedFileIds, files, sectionId, threadKey } = input;
57
58 const expandedFileIds = useMemo(
59 () => getValidReviewFileIds(files, cachedExpandedFileIds),
60 [cachedExpandedFileIds, files],
61 );
62 const viewedFileIds = useMemo(
63 () => getValidExplicitReviewFileIds(files, cachedViewedFileIds),
64 [cachedViewedFileIds, files],
65 );
66 const collapsedFileIds = useMemo(() => {
67 const expandedFileIdSet = new Set(expandedFileIds);
68 return files.reduce<string[]>((fileIds, file) => {
69 if (!expandedFileIdSet.has(file.id)) {
70 fileIds.push(file.id);
71 }
72 return fileIds;
73 }, []);
74 }, [expandedFileIds, files]);
75
76 const toggleExpandedFile = useCallback(
77 (fileId: string) => {
78 if (!threadKey || !sectionId) {
79 return;
80 }
81
82 updateReviewExpandedFileIds(threadKey, sectionId, (existing) =>
83 toggleReviewFileId(getValidReviewFileIds(files, existing), fileId),
84 );
85 },
86 [files, sectionId, threadKey],
87 );
88
89 const toggleViewedFile = useCallback(
90 (fileId: string) => {
91 if (!threadKey || !sectionId) {
92 return;
93 }
94
95 const shouldCollapse = !viewedFileIds.includes(fileId);
96 updateReviewViewedFileIds(threadKey, sectionId, (existing) =>
97 toggleReviewFileId(getValidExplicitReviewFileIds(files, existing), fileId),
98 );
99
100 if (shouldCollapse) {
101 updateReviewExpandedFileIds(threadKey, sectionId, (existing) =>
102 removeReviewFileId(getValidReviewFileIds(files, existing), fileId),
103 );
104 }
105 },
106 [files, sectionId, threadKey, viewedFileIds],
107 );
108
109 return {
110 expandedFileIds,
111 viewedFileIds,
112 collapsedFileIds,
113 toggleExpandedFile,
114 toggleViewedFile,
115 };
116}