"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports._TESTING_ONLY_reset_container_count = _TESTING_ONLY_reset_container_count;
exports.default = createNavigationContainer;

var React = _interopRequireWildcard(require("react"));

var _reactNative = require("react-native");

var _core = require("@react-navigation/core");

var _invariant = _interopRequireDefault(require("./utils/invariant"));

var _docsUrl = _interopRequireDefault(require("./utils/docsUrl"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

const {
  urlToPathAndParams
} = _core.pathUtils;

function isStateful(props) {
  return !props.navigation;
}

function validateProps(props) {
  if (props.persistenceKey) {
    console.warn('You passed persistenceKey prop to a navigator. ' + 'The persistenceKey prop was replaced by a more flexible persistence mechanism, ' + 'please see the navigation state persistence docs for more information. ' + 'Passing the persistenceKey prop is a no-op.');
  }

  if (isStateful(props)) {
    return;
  }
  /* eslint-disable no-unused-vars */


  const {
    navigation,
    screenProps,
    persistNavigationState,
    loadNavigationState,
    theme,
    ...containerProps
  } = props;
  /* eslint-enable no-unused-vars */

  const keys = Object.keys(containerProps);

  if (keys.length !== 0) {
    throw new Error('This navigator has both navigation and container props, so it is ' + "unclear if it should own its own state. Remove props: \"".concat(keys.join(', '), "\" ") + 'if the navigator should get its state from the navigation prop. If the ' + 'navigator should maintain its own state, do not pass a navigation prop.');
  }

  (0, _invariant.default)(persistNavigationState === undefined && loadNavigationState === undefined || typeof persistNavigationState === 'function' && typeof loadNavigationState === 'function', 'both persistNavigationState and loadNavigationState must either be undefined, or be functions');
} // Track the number of stateful container instances. Warn if >0 and not using the
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
// stateful navigation containers in a future release and require a provider style pattern
// instead in order to eliminate confusion entirely.


let _statefulContainerCount = 0;

function _TESTING_ONLY_reset_container_count() {
  _statefulContainerCount = 0;
} // We keep a global flag to catch errors during the state persistence hydrating scenario.
// The innermost navigator who catches the error will dispatch a new init action.


let _reactNavigationIsHydratingState = false; // Unfortunate to use global state here, but it seems necessesary for the time
// being. There seems to be some problems with cascading componentDidCatch
// handlers. Ideally the inner non-stateful navigator catches the error and
// re-throws it, to be caught by the top-level stateful navigator.

/**
 * Create an HOC that injects the navigation and manages the navigation state
 * in case it's not passed from above.
 * This allows to use e.g. the StackNavigator and TabNavigator as root-level
 * components.
 */

function createNavigationContainer(Component) {
  class NavigationContainer extends React.Component {
    static getDerivedStateFromProps(nextProps) {
      validateProps(nextProps);
      return null;
    }

    constructor(props) {
      super(props);

      _defineProperty(this, "subs", null);

      _defineProperty(this, "_actionEventSubscribers", new Set());

      _defineProperty(this, "_handleOpenURL", ({
        url
      }) => {
        const {
          enableURLHandling,
          uriPrefix
        } = this.props;

        if (enableURLHandling === false) {
          return;
        }

        const parsedUrl = urlToPathAndParams(url, uriPrefix);

        if (parsedUrl) {
          const {
            path,
            params
          } = parsedUrl;
          const action = Component.router.getActionForPathAndParams(path, params);

          if (action) {
            this.dispatch(action);
          }
        }
      });

      _defineProperty(this, "_persistNavigationState", async nav => {
        const {
          persistNavigationState
        } = this.props;

        if (persistNavigationState) {
          try {
            await persistNavigationState(nav);
          } catch (err) {
            console.warn('Uncaught exception while calling persistNavigationState()! You should handle exceptions thrown from persistNavigationState(), ignoring them may result in undefined behavior.');
          }
        }
      });

      _defineProperty(this, "dispatch", action => {
        if (this.props.navigation) {
          return this.props.navigation.dispatch(action);
        } // navState will have the most up-to-date value, because setState sometimes behaves asyncronously


        this._navState = this._navState || this.state.nav;
        const lastNavState = this._navState;
        (0, _invariant.default)(lastNavState, 'should be set in constructor if stateful');
        const reducedState = Component.router.getStateForAction(action, lastNavState);
        const navState = reducedState === null ? lastNavState : reducedState;

        const dispatchActionEvents = () => {
          this._actionEventSubscribers.forEach(subscriber => subscriber({
            type: 'action',
            action,
            state: navState,
            lastState: lastNavState
          }));
        };

        if (reducedState === null) {
          // The router will return null when action has been handled and the state hasn't changed.
          // dispatch returns true when something has been handled.
          dispatchActionEvents();
          return true;
        }

        if (navState !== lastNavState) {
          // Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
          this._navState = navState;
          this.setState({
            nav: navState
          }, () => {
            this._onNavigationStateChange(lastNavState, navState, action);

            dispatchActionEvents();

            this._persistNavigationState(navState);
          });
          return true;
        }

        dispatchActionEvents();
        return false;
      });

      _defineProperty(this, "_getScreenProps", () => this.props.screenProps);

      _defineProperty(this, "_getTheme", () => {
        if (this.props.theme === 'light' || this.props.theme === 'dark') {
          return this.props.theme;
        } else if (this.props.theme === 'no-preference') {
          return 'light';
        } else {
          console.warn("Invalid theme provided: ".concat(this.props.theme, ". Only 'light' and 'dark' are supported. Falling back to 'light'"));
          return 'light';
        }
      });

      validateProps(props);
      this._initialAction = _core.NavigationActions.init();

      if (this._isStateful() && _reactNative.BackHandler && typeof _reactNative.BackHandler.addEventListener === 'function') {
        this.subs = _reactNative.BackHandler.addEventListener('hardwareBackPress', () => {
          if (!this._isMounted) {
            this.subs && this.subs.remove();
          } else {
            // dispatch returns true if the action results in a state change,
            // and false otherwise. This maps well to what BackHandler expects
            // from a callback -- true if handled, false if not handled
            return this.dispatch(_core.NavigationActions.back());
          }
        });
      }

      this.state = {
        nav: this._isStateful() && !props.loadNavigationState ? Component.router.getStateForAction(this._initialAction) : null
      };
    }

    _renderLoading() {
      return this.props.renderLoadingExperimental ? this.props.renderLoadingExperimental() : null;
    }

    _isStateful() {
      return isStateful(this.props);
    }

    _onNavigationStateChange(prevNav, nav, action) {
      if (typeof this.props.onNavigationStateChange === 'undefined' && this._isStateful() && !!process.env.REACT_NAV_LOGGING) {
        if (console.group) {
          console.group('Navigation Dispatch: ');
          console.log('Action: ', action);
          console.log('New State: ', nav);
          console.log('Last State: ', prevNav);
          console.groupEnd();
        } else {
          console.log('Navigation Dispatch: ', {
            action,
            newState: nav,
            lastState: prevNav
          });
        }

        return;
      }

      if (typeof this.props.onNavigationStateChange === 'function') {
        this.props.onNavigationStateChange(prevNav, nav, action);
      }
    }

    componentDidUpdate() {
      // Clear cached _navState every tick
      if (this._navState === this.state.nav) {
        this._navState = null;
      }
    }

    async componentDidMount() {
      this._isMounted = true;

      if (!this._isStateful()) {
        return;
      }

      if (process.env.NODE_ENV !== 'production' && !this.props.detached) {
        if (_statefulContainerCount > 0) {
          // Temporarily only show this on iOS due to this issue:
          // https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
          if (_reactNative.Platform.OS === 'ios') {
            console.warn("You should only render one navigator explicitly in your app, and other navigators should be rendered by including them in that navigator. Full details at: ".concat((0, _docsUrl.default)('common-mistakes.html#explicitly-rendering-more-than-one-navigator')));
          }
        }
      }

      _statefulContainerCount++;

      _reactNative.Linking.addEventListener('url', this._handleOpenURL); // Pull out anything that can impact state


      let parsedUrl = null;
      let userProvidedStartupState = null;

      if (this.props.enableURLHandling !== false) {
        ({
          parsedUrl,
          userProvidedStartupState
        } = await this.getStartupParams());
      } // Initialize state. This must be done *after* any async code
      // so we don't end up with a different value for this.state.nav
      // due to changes while async function was resolving


      let action = this._initialAction; // eslint-disable-next-line react/no-access-state-in-setstate

      let startupState = this.state.nav;

      if (!startupState && !userProvidedStartupState) {
        !!process.env.REACT_NAV_LOGGING && console.log('Init new Navigation State');
        startupState = Component.router.getStateForAction(action);
      } // Pull user-provided persisted state


      if (userProvidedStartupState) {
        startupState = userProvidedStartupState;
        _reactNavigationIsHydratingState = true;
      } // Pull state out of URL


      if (parsedUrl) {
        const {
          path,
          params
        } = parsedUrl;
        const urlAction = Component.router.getActionForPathAndParams(path, params);

        if (urlAction) {
          !!process.env.REACT_NAV_LOGGING && console.log('Applying Navigation Action for Initial URL:', parsedUrl);
          action = urlAction;
          startupState = Component.router.getStateForAction(urlAction, startupState);
        }
      }

      const dispatchActions = () => this._actionEventSubscribers.forEach(subscriber => subscriber({
        type: 'action',
        action,
        state: this.state.nav,
        lastState: null
      }));

      if (startupState === this.state.nav) {
        dispatchActions();
        return;
      } // eslint-disable-next-line react/no-did-mount-set-state


      this.setState({
        nav: startupState
      }, () => {
        _reactNavigationIsHydratingState = false;
        dispatchActions();
      });
    }

    async getStartupParams() {
      const {
        uriPrefix,
        loadNavigationState
      } = this.props;
      let url, loadedNavState;

      try {
        [url, loadedNavState] = await Promise.all([_reactNative.Linking.getInitialURL(), loadNavigationState && loadNavigationState()]);
      } catch (err) {// ignore
      }

      return {
        parsedUrl: url && urlToPathAndParams(url, uriPrefix),
        userProvidedStartupState: loadedNavState
      };
    }

    componentDidCatch(e) {
      if (_reactNavigationIsHydratingState) {
        _reactNavigationIsHydratingState = false;
        console.warn('Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state...');
        this.dispatch(_core.NavigationActions.init());
      } else {
        throw e;
      }
    }

    componentWillUnmount() {
      this._isMounted = false;

      _reactNative.Linking.removeEventListener('url', this._handleOpenURL);

      this.subs && this.subs.remove();

      if (this._isStateful()) {
        _statefulContainerCount--;
      }
    } // Per-tick temporary storage for state.nav


    render() {
      let navigation = this.props.navigation;

      if (this._isStateful()) {
        const navState = this.state.nav;

        if (!navState) {
          return this._renderLoading();
        }

        if (!this._navigation || this._navigation.state !== navState) {
          this._navigation = (0, _core.getNavigation)(Component.router, navState, this.dispatch, this._actionEventSubscribers, this._getScreenProps, () => this._navigation);
        }

        navigation = this._navigation;
      }

      (0, _invariant.default)(navigation, 'failed to get navigation');
      return /*#__PURE__*/React.createElement(_core.ThemeProvider, {
        value: this._getTheme()
      }, /*#__PURE__*/React.createElement(_core.NavigationProvider, {
        value: navigation
      }, /*#__PURE__*/React.createElement(Component, _extends({}, this.props, {
        navigation: navigation
      }))));
    }

  }

  _defineProperty(NavigationContainer, "router", Component.router);

  _defineProperty(NavigationContainer, "navigationOptions", null);

  _defineProperty(NavigationContainer, "defaultProps", {
    theme: 'light'
  });

  return NavigationContainer;
}
//# sourceMappingURL=createAppContainer.js.map