1import type { NavigationState } from "@react-navigation/native";
2
3export type AdaptiveNavigationAction = "push" | "replace" | "set-params";
4
5const BASE_THREAD_ROUTE_PATTERN = /^\/threads\/[^/]+\/[^/]+\/?$/;
6
7export function isBaseThreadRoute(pathname: string): boolean {
8 return BASE_THREAD_ROUTE_PATTERN.test(pathname);
9}
10
11/**
12 * A persistent sidebar selects a peer destination in place. A compact list
13 * drills into a new destination so the native back stack remains available.
14 * From Home the selection pushes (never replaces) so Home stays beneath the
15 * thread — collapsing back to a compact width keeps a sane back stack.
16 */
17export function resolveThreadSelectionNavigationAction(input: {
18 readonly usesSplitView: boolean;
19 readonly pathname: string;
20}): AdaptiveNavigationAction {
21 if (!input.usesSplitView || input.pathname === "/") {
22 return "push";
23 }
24
25 return isBaseThreadRoute(input.pathname) ? "set-params" : "replace";
26}
27
28/** Dismiss sheets and select their underlying workspace destination in one stack update. */
29export function resolveThreadSelectionOverlayState(input: {
30 readonly state: NavigationState | undefined;
31 readonly workspaceRouteKey: string | undefined;
32 readonly action: AdaptiveNavigationAction;
33 readonly params: ReactNavigation.RootParamList["Thread"];
34}) {
35 if (input.state === undefined) return null;
36 const workspaceIndex = input.state.routes.findIndex(
37 (route) => route.key === input.workspaceRouteKey,
38 );
39 if (workspaceIndex < 0 || workspaceIndex >= input.state.index) return null;
40
41 const workspaceRoute = input.state.routes[workspaceIndex];
42 const routes = input.state.routes.slice(0, workspaceIndex + (input.action === "push" ? 1 : 0));
43 return {
44 ...input.state,
45 index: routes.length,
46 routes: [
47 ...routes,
48 input.action === "set-params" && workspaceRoute?.name === "Thread"
49 ? { ...workspaceRoute, params: { ...workspaceRoute.params, ...input.params } }
50 : { name: "Thread", params: input.params },
51 ],
52 };
53}
54
55/**
56 * On regular-width layouts, the file browser and preview occupy one workspace
57 * destination. Replacing the browser route keeps a single back step to chat.
58 * Compact layouts retain the browser as the previous stack screen.
59 */
60export function resolveFileSelectionNavigationAction(input: {
61 readonly hasPersistentFileInspector: boolean;
62}): AdaptiveNavigationAction {
63 return input.hasPersistentFileInspector ? "replace" : "push";
64}