1import { useCallback, useMemo, useState } from "react";
2import type { NativeSyntheticEvent } from "react-native";
3
4import { createNativeReviewDiffTheme, type NativeReviewDiffData } from "./nativeReviewDiffAdapter";
5import { useAppearanceCodeSurface } from "../settings/appearance/useAppearanceCodeSurface";
6import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
7import { useNativeReviewDiffHighlighting } from "./useNativeReviewDiffHighlighting";
8import { buildNativeReviewTokensResetKey } from "./reviewDiffBridgeKeys";
9import { useUniwindTheme } from "../../lib/useUniwindTheme";
10
11export { buildNativeReviewTokensResetKey } from "./reviewDiffBridgeKeys";
12
13export function useNativeReviewDiffBridge(input: {
14 readonly threadKey: string | null;
15 readonly sectionId: string | null;
16 readonly diff: string | null | undefined;
17 readonly data: NativeReviewDiffData;
18 readonly collapsedFileIds: ReadonlyArray<string>;
19 readonly viewedFileIds: ReadonlyArray<string>;
20 readonly selectedRowIds: ReadonlyArray<string>;
21 readonly canHighlight: boolean;
22}) {
23 const {
24 canHighlight,
25 collapsedFileIds,
26 data,
27 diff,
28 sectionId,
29 selectedRowIds,
30 threadKey,
31 viewedFileIds,
32 } = input;
33 const { nativeReviewDiffStyle } = useAppearanceCodeSurface();
34 const { themeAppearance: scheme, themeId } = useAppearancePreferences();
35 const appTheme = useUniwindTheme();
36 const [collapsedCommentIds, setCollapsedCommentIds] = useState<ReadonlySet<string>>(
37 () => new Set(),
38 );
39
40 const theme = useMemo(
41 () => createNativeReviewDiffTheme(scheme, themeId, appTheme),
42 [appTheme, scheme, themeId],
43 );
44 const rowsJson = useMemo(() => JSON.stringify(data.rows), [data.rows]);
45 const collapsedFileIdsJson = useMemo(() => JSON.stringify(collapsedFileIds), [collapsedFileIds]);
46 const viewedFileIdsJson = useMemo(() => JSON.stringify(viewedFileIds), [viewedFileIds]);
47 const selectedRowIdsJson = useMemo(() => JSON.stringify(selectedRowIds), [selectedRowIds]);
48 const collapsedCommentIdsJson = useMemo(
49 () => JSON.stringify(Array.from(collapsedCommentIds)),
50 [collapsedCommentIds],
51 );
52 const themeJson = useMemo(() => JSON.stringify(theme), [theme]);
53 const styleJson = useMemo(() => JSON.stringify(nativeReviewDiffStyle), [nativeReviewDiffStyle]);
54 const tokensResetKey = useMemo(
55 () =>
56 buildNativeReviewTokensResetKey({
57 threadKey,
58 sectionId,
59 scheme,
60 diff,
61 fileCount: data.files.length,
62 rowCount: data.rows.length,
63 }),
64 [data.files.length, data.rows.length, diff, scheme, sectionId, threadKey],
65 );
66 const { tokensPatchJson, updateVisibleRange } = useNativeReviewDiffHighlighting({
67 files: data.files,
68 rows: data.rows,
69 scheme,
70 resetKey: tokensResetKey,
71 enabled: canHighlight,
72 });
73
74 const onDebug = useCallback(
75 (event: NativeSyntheticEvent<Record<string, unknown>>) => {
76 const payload = event.nativeEvent;
77 const message = payload.message;
78 if (
79 (message === "draw-metrics" || message === "visible-range") &&
80 typeof payload.firstRowIndex === "number" &&
81 typeof payload.lastRowIndex === "number"
82 ) {
83 updateVisibleRange({
84 firstRowIndex: payload.firstRowIndex,
85 lastRowIndex: payload.lastRowIndex,
86 });
87 }
88 },
89 [updateVisibleRange],
90 );
91
92 const onToggleComment = useCallback(
93 (event: NativeSyntheticEvent<{ readonly commentId?: string }>) => {
94 const { commentId } = event.nativeEvent;
95 if (!commentId) {
96 return;
97 }
98
99 setCollapsedCommentIds((current) => {
100 const next = new Set(current);
101 if (next.has(commentId)) {
102 next.delete(commentId);
103 } else {
104 next.add(commentId);
105 }
106 return next;
107 });
108 },
109 [],
110 );
111
112 return {
113 themeId,
114 theme,
115 rowsJson,
116 collapsedFileIdsJson,
117 collapsedCommentIdsJson,
118 viewedFileIdsJson,
119 selectedRowIdsJson,
120 themeJson,
121 styleJson,
122 tokensPatchJson,
123 tokensResetKey,
124 onDebug,
125 onToggleComment,
126 };
127}