1import type { RouteNode } from './Route';
2import { matchDeepDynamicRouteName, matchDynamicName } from './matchers';
3
4export type Screen =
5  | string
6  | {
7      path: string;
8      screens: Record<string, Screen>;
9      _route?: RouteNode;
10      initialRouteName?: string;
11    };
12
13// `[page]` -> `:page`
14// `page` -> `page`
15function convertDynamicRouteToReactNavigation(segment: string): string {
16  // NOTE(EvanBacon): To support shared routes we preserve group segments.
17  if (segment === 'index') {
18    return '';
19  }
20
21  const rest = matchDeepDynamicRouteName(segment);
22  if (rest != null) {
23    return '*' + rest;
24  }
25  const dynamicName = matchDynamicName(segment);
26
27  if (dynamicName != null) {
28    return `:${dynamicName}`;
29  }
30
31  return segment;
32}
33
34function parseRouteSegments(segments: string): string {
35  return (
36    // NOTE(EvanBacon): When there are nested routes without layouts
37    // the node.route will be something like `app/home/index`
38    // this needs to be split to ensure each segment is parsed correctly.
39    segments
40      .split('/')
41      // Convert each segment to a React Navigation format.
42      .map(convertDynamicRouteToReactNavigation)
43      // Remove any empty paths from groups or index routes.
44      .filter(Boolean)
45      // Join to return as a path.
46      .join('/')
47  );
48}
49
50function convertRouteNodeToScreen(node: RouteNode, metaOnly: boolean): Screen {
51  const path = parseRouteSegments(node.route);
52  if (!node.children.length) {
53    if (!metaOnly) {
54      return {
55        path,
56        screens: {},
57        _route: node,
58      };
59    }
60    return path;
61  }
62  const screens = getReactNavigationScreensConfig(node.children, metaOnly);
63
64  const screen: Screen = {
65    path,
66    screens,
67    // NOTE(EvanBacon): This is bad because it forces all Layout Routes
68    // to be loaded into memory. We should move towards a system where
69    // the initial route name is either loaded asynchronously in the Layout Route
70    // or defined via a file system convention.
71    initialRouteName: node.initialRouteName,
72  };
73
74  if (!metaOnly) {
75    screen._route = node;
76  }
77
78  return screen;
79}
80
81export function getReactNavigationScreensConfig(
82  nodes: RouteNode[],
83  metaOnly: boolean
84): Record<string, Screen> {
85  return Object.fromEntries(
86    nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)] as const)
87  );
88}
89
90export function getReactNavigationConfig(
91  routes: RouteNode,
92  metaOnly: boolean
93): {
94  initialRouteName?: string;
95  screens: Record<string, Screen>;
96} {
97  return {
98    initialRouteName: routes.initialRouteName,
99    screens: getReactNavigationScreensConfig(routes.children, metaOnly),
100  };
101}
102