"use strict";

import { CommonActions } from '@react-navigation/routers';
import * as React from 'react';
import { NavigationBuilderContext } from "./NavigationBuilderContext.js";
/**
 * Hook to cache navigation objects for each screen in the navigator.
 * It's important to cache them to make sure navigation objects don't change between renders.
 * This lets us apply optimizations like `React.memo` to minimize re-rendering screens.
 */
export function useNavigationCache({
  state,
  getState,
  navigation,
  setOptions,
  router,
  emitter
}) {
  const {
    stackRef
  } = React.useContext(NavigationBuilderContext);
  const base = React.useMemo(() => {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const {
      emit,
      ...rest
    } = navigation;
    const actions = {
      ...router.actionCreators,
      ...CommonActions
    };
    const dispatch = () => {
      throw new Error('Actions cannot be dispatched from a placeholder screen.');
    };
    const helpers = Object.keys(actions).reduce((acc, name) => {
      acc[name] = dispatch;
      return acc;
    }, {});
    return {
      ...rest,
      ...helpers,
      addListener: () => {
        // Event listeners are not supported for placeholder screens

        return () => {
          // Empty function
        };
      },
      removeListener: () => {
        // Event listeners are not supported for placeholder screens
      },
      dispatch,
      getParent: id => {
        if (id !== undefined && id === rest.getId()) {
          return base;
        }
        return rest.getParent(id);
      },
      setOptions: () => {
        throw new Error('Options cannot be set from a placeholder screen.');
      },
      isFocused: () => false
    };
  }, [navigation, router.actionCreators]);

  // Cache object which holds navigation objects for each screen
  // We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
  // In reality, these deps will rarely change, if ever
  const cache = React.useMemo(() => ({
    current: {}
  }),
  // eslint-disable-next-line react-hooks/exhaustive-deps
  [base, getState, navigation, setOptions, emitter]);
  cache.current = state.routes.reduce((acc, route) => {
    const previous = cache.current[route.key];
    if (previous) {
      // If a cached navigation object already exists, reuse it
      acc[route.key] = previous;
    } else {
      const dispatch = thunk => {
        const action = typeof thunk === 'function' ? thunk(getState()) : thunk;
        if (action != null) {
          navigation.dispatch({
            source: route.key,
            ...action
          });
        }
      };
      const withStack = callback => {
        let isStackSet = false;
        try {
          if (process.env.NODE_ENV !== 'production' && stackRef && !stackRef.current) {
            // Capture the stack trace for devtools
            stackRef.current = new Error().stack;
            isStackSet = true;
          }
          callback();
        } finally {
          if (isStackSet && stackRef) {
            stackRef.current = undefined;
          }
        }
      };
      const actions = {
        ...router.actionCreators,
        ...CommonActions
      };
      const helpers = Object.keys(actions).reduce((acc, name) => {
        acc[name] = (...args) => withStack(() =>
        // @ts-expect-error: name is a valid key, but TypeScript is dumb
        dispatch(actions[name](...args)));
        return acc;
      }, {});
      acc[route.key] = {
        ...base,
        ...helpers,
        // FIXME: too much work to fix the types for now
        ...emitter.create(route.key),
        dispatch: thunk => withStack(() => dispatch(thunk)),
        getParent: id => {
          if (id !== undefined && id === base.getId()) {
            // If the passed id is the same as the current navigation id,
            // we return the cached navigation object for the relevant route
            return acc[route.key];
          }
          return base.getParent(id);
        },
        setOptions: options => {
          setOptions(o => ({
            ...o,
            [route.key]: {
              ...o[route.key],
              ...options
            }
          }));
        },
        isFocused: () => {
          const state = base.getState();
          if (state.routes[state.index].key !== route.key) {
            return false;
          }

          // If the current screen is focused, we also need to check if parent navigator is focused
          // This makes sure that we return the focus state in the whole tree, not just this navigator
          return navigation ? navigation.isFocused() : true;
        }
      };
    }
    return acc;
  }, {});
  return {
    base,
    navigations: cache.current
  };
}
//# sourceMappingURL=useNavigationCache.js.map