1import type { RefObject } from "react";
2
3import {
4 isNativeReviewDiffDrawEvent,
5 type NativeReviewDiffViewHandle,
6 type NativeReviewDiffViewProps,
7} from "../diffs/nativeReviewDiffSurface";
8
9export function pullRequestDiffInitialPosition(
10 targetFileId: string | undefined,
11 focusIndex: number,
12): Pick<NativeReviewDiffViewProps, "initialRowIndex" | "initialFileId"> {
13 // Explicit empty values also clear a previous destination on a reused native view.
14 return {
15 initialRowIndex: focusIndex >= 0 ? Math.max(0, focusIndex - 3) : -1,
16 initialFileId: focusIndex >= 0 ? "" : (targetFileId ?? ""),
17 };
18}
19
20export function handlePullRequestDiffDebugEvent(
21 event: Parameters<NonNullable<NativeReviewDiffViewProps["onDebug"]>>[0],
22 input: {
23 readonly rowCount: number;
24 readonly targetFileId: string | undefined;
25 readonly focusIndex: number;
26 readonly isIos: boolean;
27 readonly nativeView: RefObject<NativeReviewDiffViewHandle | null>;
28 readonly jumpStarted: () => boolean;
29 readonly setJumpStarted: (started: boolean) => void;
30 readonly onDebug: NonNullable<NativeReviewDiffViewProps["onDebug"]>;
31 readonly loadMore: () => void;
32 },
33) {
34 const drawn = isNativeReviewDiffDrawEvent(event.nativeEvent);
35 // Delivery outcomes and content mounts must reach the bridge even before rows decode.
36 if (!drawn) input.onDebug(event);
37 const decoded =
38 event.nativeEvent.message === "rows-decoded" && event.nativeEvent.rows === input.rowCount;
39 if (!drawn && !decoded) return;
40 // Filtering replaces the native row payload asynchronously. Old draw events
41 // must not consume the file jump or highlight the new payload's row indices.
42 if (
43 typeof event.nativeEvent.totalRows === "number" &&
44 event.nativeEvent.totalRows !== input.rowCount
45 )
46 return;
47 if (drawn) input.onDebug(event);
48 if (
49 input.targetFileId &&
50 !input.jumpStarted() &&
51 input.nativeView.current &&
52 (!input.isIos || decoded)
53 ) {
54 input.setJumpStarted(true);
55 if (input.focusIndex >= 0) return;
56 void input.nativeView.current.scrollToFile(input.targetFileId, false).catch(() => {
57 input.setJumpStarted(false);
58 });
59 return;
60 }
61 if (!drawn) return;
62 const lastRow = event.nativeEvent.lastRowIndex;
63 if (typeof lastRow === "number" && lastRow >= input.rowCount - 40) input.loadMore();
64}