1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.useInitializeExpoRouter = exports.useStoreRouteInfo = exports.useStoreRootState = exports.useExpoRouter = exports.store = exports.RouterStore = void 0;
4const native_1 = require("@react-navigation/native");
5const react_1 = require("react");
6const routing_1 = require("./routing");
7const sort_routes_1 = require("./sort-routes");
8const LocationProvider_1 = require("../LocationProvider");
9const getPathFromState_1 = require("../fork/getPathFromState");
10const getLinkingConfig_1 = require("../getLinkingConfig");
11const getRoutes_1 = require("../getRoutes");
12const useScreens_1 = require("../useScreens");
13const Splash_1 = require("../views/Splash");
14/**
15 * This is the global state for the router. It is used to keep track of the current route, and to provide a way to navigate to other routes.
16 *
17 * There should only be one instance of this class and be initialized via `useInitializeExpoRouter`
18 */
19class RouterStore {
20    routeNode;
21    rootComponent;
22    linking;
23    hasAttemptedToHideSplash = false;
24    initialState;
25    rootState;
26    nextState;
27    routeInfo;
28    navigationRef;
29    navigationRefSubscription;
30    rootStateSubscribers = new Set();
31    storeSubscribers = new Set();
32    linkTo = routing_1.linkTo.bind(this);
33    getSortedRoutes = sort_routes_1.getSortedRoutes.bind(this);
34    goBack = routing_1.goBack.bind(this);
35    canGoBack = routing_1.canGoBack.bind(this);
36    push = routing_1.push.bind(this);
37    replace = routing_1.replace.bind(this);
38    setParams = routing_1.setParams.bind(this);
39    initialize(context, navigationRef, initialLocation) {
40        // Clean up any previous state
41        this.initialState = undefined;
42        this.rootState = undefined;
43        this.nextState = undefined;
44        this.routeInfo = undefined;
45        this.linking = undefined;
46        this.navigationRefSubscription?.();
47        this.rootStateSubscribers.clear();
48        this.storeSubscribers.clear();
49        this.routeNode = (0, getRoutes_1.getRoutes)(context);
50        this.rootComponent = this.routeNode ? (0, useScreens_1.getQualifiedRouteComponent)(this.routeNode) : react_1.Fragment;
51        // Only error in production, in development we will show the onboarding screen
52        if (!this.routeNode && process.env.NODE_ENV === 'production') {
53            throw new Error('No routes found');
54        }
55        this.navigationRef = navigationRef;
56        if (this.routeNode) {
57            this.linking = (0, getLinkingConfig_1.getLinkingConfig)(this.routeNode);
58            if (initialLocation) {
59                this.linking.getInitialURL = () => initialLocation.toString();
60                this.initialState = this.linking.getStateFromPath?.(initialLocation.pathname + initialLocation.search, this.linking.config);
61            }
62        }
63        // There is no routeNode, so we will be showing the onboarding screen
64        // In the meantime, just mock the routeInfo
65        if (this.initialState) {
66            this.rootState = this.initialState;
67            this.routeInfo = this.getRouteInfo(this.initialState);
68        }
69        else {
70            this.routeInfo = {
71                unstable_globalHref: '',
72                pathname: '',
73                params: {},
74                segments: [],
75            };
76        }
77        /**
78         * Counter intuitively - this fires AFTER both React Navigations state change and the subsequent paint.
79         * This poses a couple of issues for Expo Router,
80         *   - Ensuring hooks (e.g. useSearchParams()) have data in the initial render
81         *   - Reacting to state changes after a navigation event
82         *
83         * This is why the initial render renders a Fragment and we wait until `onReady()` is called
84         * Additionally, some hooks compare the state from both the store and the navigationRef. If the store it stale,
85         * that hooks will manually update the store.
86         *
87         */
88        this.navigationRefSubscription = navigationRef.addListener('state', (data) => {
89            const state = data.data.state;
90            if (!this.hasAttemptedToHideSplash) {
91                this.hasAttemptedToHideSplash = true;
92                // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially.
93                requestAnimationFrame(() => (0, Splash_1._internal_maybeHideAsync)());
94            }
95            let shouldUpdateSubscribers = this.nextState === state;
96            this.nextState = undefined;
97            // This can sometimes be undefined when an error is thrown in the Root Layout Route.
98            // Additionally that state may already equal the rootState if it was updated within a hook
99            if (state && state !== this.rootState) {
100                exports.store.updateState(state, undefined);
101                shouldUpdateSubscribers = true;
102            }
103            // If the state has changed, or was changed inside a hook we need to update the subscribers
104            if (shouldUpdateSubscribers) {
105                for (const subscriber of this.rootStateSubscribers) {
106                    subscriber();
107                }
108            }
109        });
110        for (const subscriber of this.storeSubscribers) {
111            subscriber();
112        }
113    }
114    updateState(state, nextState = state) {
115        exports.store.rootState = state;
116        exports.store.nextState = nextState;
117        const nextRouteInfo = exports.store.getRouteInfo(state);
118        if (!(0, getPathFromState_1.deepEqual)(this.routeInfo, nextRouteInfo)) {
119            exports.store.routeInfo = nextRouteInfo;
120        }
121    }
122    getRouteInfo(state) {
123        return (0, LocationProvider_1.getRouteInfoFromState)((state, asPath) => {
124            return (0, getPathFromState_1.getPathDataFromState)(state, {
125                screens: [],
126                ...this.linking?.config,
127                preserveDynamicRoutes: asPath,
128                preserveGroups: asPath,
129            });
130        }, state);
131    }
132    // This is only used in development, to show the onboarding screen
133    // In production we should have errored during the initialization
134    shouldShowTutorial() {
135        return !this.routeNode && process.env.NODE_ENV === 'development';
136    }
137    /** Make sure these are arrow functions so `this` is correctly bound */
138    subscribeToRootState = (subscriber) => {
139        this.rootStateSubscribers.add(subscriber);
140        return () => this.rootStateSubscribers.delete(subscriber);
141    };
142    subscribeToStore = (subscriber) => {
143        this.storeSubscribers.add(subscriber);
144        return () => this.storeSubscribers.delete(subscriber);
145    };
146    snapshot = () => {
147        return this;
148    };
149    rootStateSnapshot = () => {
150        return this.rootState;
151    };
152    routeInfoSnapshot = () => {
153        return this.routeInfo;
154    };
155}
156exports.RouterStore = RouterStore;
157exports.store = new RouterStore();
158function useExpoRouter() {
159    return (0, react_1.useSyncExternalStore)(exports.store.subscribeToStore, exports.store.snapshot, exports.store.snapshot);
160}
161exports.useExpoRouter = useExpoRouter;
162function syncStoreRootState() {
163    if (exports.store.navigationRef.isReady()) {
164        const currentState = exports.store.navigationRef.getRootState();
165        if (exports.store.rootState !== currentState) {
166            exports.store.updateState(currentState);
167        }
168    }
169}
170function useStoreRootState() {
171    syncStoreRootState();
172    return (0, react_1.useSyncExternalStore)(exports.store.subscribeToRootState, exports.store.rootStateSnapshot, exports.store.rootStateSnapshot);
173}
174exports.useStoreRootState = useStoreRootState;
175function useStoreRouteInfo() {
176    syncStoreRootState();
177    return (0, react_1.useSyncExternalStore)(exports.store.subscribeToRootState, exports.store.routeInfoSnapshot, exports.store.routeInfoSnapshot);
178}
179exports.useStoreRouteInfo = useStoreRouteInfo;
180function useInitializeExpoRouter(context, initialLocation) {
181    const navigationRef = (0, native_1.useNavigationContainerRef)();
182    (0, react_1.useMemo)(() => exports.store.initialize(context, navigationRef, initialLocation), [context, initialLocation]);
183    useExpoRouter();
184    return exports.store;
185}
186exports.useInitializeExpoRouter = useInitializeExpoRouter;
187//# sourceMappingURL=router-store.js.map