text: /* @flow */
  
  import React from 'react';
  
  import {
    Animated,
    Easing,
    StyleSheet,
    View,
  } from 'react-native';
  
  import invariant from 'fbjs/lib/invariant';
  
  import NavigationScenesReducer from './ScenesReducer';
  import NavigationPropTypes from '../PropTypes';
  import TransitionConfigs from './TransitionConfigs';
  
  import type {
    NavigationAnimatedValue,
    NavigationLayout,
    NavigationScene,
    NavigationState,
    NavigationTransitionProps,
    NavigationTransitionSpec,
  } from '../TypeDefinition';
  
  type Props = {
    configureTransition: (
      transitionProps: NavigationTransitionProps,
      prevTransitionProps: ?NavigationTransitionProps,
    ) => NavigationTransitionSpec,
    navigationState: NavigationState,
    onTransitionEnd: () => void,
    onTransitionStart: () => void,
    render: (
      transitionProps: NavigationTransitionProps,
      prevTransitionProps: ?NavigationTransitionProps
    ) => any,
    style: any,
  };
  
  type State = {
    layout: NavigationLayout,
    position: NavigationAnimatedValue,
    progress: NavigationAnimatedValue,
    scenes: Array<NavigationScene>,
  };
  
  const { PropTypes } = React;
  
  const DefaultTransitionSpec = TransitionConfigs.DefaultTransitionSpec;
  
  class Transitioner extends React.Component<*, Props, State> {
    _onLayout: (event: any) => void;
    _onTransitionEnd: () => void;
    _prevTransitionProps: ?NavigationTransitionProps;
    _transitionProps: NavigationTransitionProps;
    _isMounted: boolean;
  
    props: Props;
    state: State;
  
    static propTypes = {
      configureTransition: PropTypes.func,
      navigationState: NavigationPropTypes.navigationState.isRequired,
      onTransitionEnd: PropTypes.func,
      onTransitionStart: PropTypes.func,
      render: PropTypes.func.isRequired,
    };
  
    constructor(props: Props, context: any) {
      super(props, context);
  
      // The initial layout isn't measured. Measured layout will be only available
      // when the component is mounted.
      const layout = {
        height: new Animated.Value(0),
        initHeight: 0,
        initWidth: 0,
        isMeasured: false,
        width: new Animated.Value(0),
      };
  
      this.state = {
        layout,
        position: new Animated.Value(this.props.navigationState.index),
        progress: new Animated.Value(1),
        scenes: NavigationScenesReducer([], this.props.navigationState),
      };
  
      this._prevTransitionProps = null;
      this._transitionProps = buildTransitionProps(props, this.state);
      this._isMounted = false;
    }
  
    componentWillMount(): void {
      this._onLayout = this._onLayout.bind(this);
      this._onTransitionEnd = this._onTransitionEnd.bind(this);
    }
  
    componentDidMount(): void {
      this._isMounted = true;
    }
  
    componentWillUnmount(): void {
      this._isMounted = false;
    }
  
    componentWillReceiveProps(nextProps: Props): void {
      const nextScenes = NavigationScenesReducer(
        this.state.scenes,
        nextProps.navigationState,
        this.props.navigationState
      );
  
      if (nextScenes === this.state.scenes) {
        return;
      }
  
      const nextState = {
        ...this.state,
        scenes: nextScenes,
      };
  
      const {
        position,
        progress,
      } = nextState;
  
      progress.setValue(0);
  
      this._prevTransitionProps = this._transitionProps;
      this._transitionProps = buildTransitionProps(nextProps, nextState);
  
       // get the transition spec.
      const transitionUserSpec = nextProps.configureTransition ?
        nextProps.configureTransition(
          this._transitionProps,
          this._prevTransitionProps,
        ) :
        null;
  
      const transitionSpec = {
        ...DefaultTransitionSpec,
        ...transitionUserSpec,
      };
  
      const { timing } = transitionSpec;
      delete transitionSpec.timing;
  
      const animations = [
        timing(
          progress,
          {
            ...transitionSpec,
            toValue: 1,
          },
        ),
      ];
  
      if (nextProps.navigationState.index !== this.props.navigationState.index) {
        animations.push(
          timing(
            position,
            {
              ...transitionSpec,
              toValue: nextProps.navigationState.index,
            },
          ),
        );
      }
  
      // update scenes and play the transition
      this.setState(nextState, () => {
        nextProps.onTransitionStart && nextProps.onTransitionStart(
          this._transitionProps,
          this._prevTransitionProps,
        );
        Animated.parallel(animations).start(this._onTransitionEnd);
      });
    }
  
    render() {
      return (
        <View
          onLayout={this._onLayout}
          style={[styles.main, this.props.style]}
        >
          {this.props.render(this._transitionProps, this._prevTransitionProps)}
        </View>
      );
    }
  
    _onLayout(event: any): void {
      const { height, width } = event.nativeEvent.layout;
      if (this.state.layout.initWidth === width &&
        this.state.layout.initHeight === height) {
        return;
      }
      const layout = {
        ...this.state.layout,
        initHeight: height,
        initWidth: width,
        isMeasured: true,
      };
  
      layout.height.setValue(height);
      layout.width.setValue(width);
  
      const nextState = {
        ...this.state,
        layout,
      };
  
      this._transitionProps = buildTransitionProps(this.props, nextState);
      this.setState(nextState);
    }
  
    _onTransitionEnd(): void {
      if (!this._isMounted) {
        return;
      }
      const prevTransitionProps = this._prevTransitionProps;
      this._prevTransitionProps = null;
  
      const nextState = {
        ...this.state,
        scenes: this.state.scenes.filter(isSceneNotStale),
      };
  
      this._transitionProps = buildTransitionProps(this.props, nextState);
  
      this.setState(nextState, () => {
        this.props.onTransitionEnd && this.props.onTransitionEnd(
          this._transitionProps,
          prevTransitionProps,
        );
      });
    }
  }
  
  function buildTransitionProps(
    props: Props,
    state: State,
  ): NavigationTransitionProps {
    const {
      navigationState,
    } = props;
  
    const {
      layout,
      position,
      progress,
      scenes,
    } = state;
  
    const scene = scenes.find(isSceneActive);
  
    invariant(scene, 'Could not find active scene');
  
    return {
      layout,
      navigationState,
      position,
      progress,
      scenes,
      scene,
      index: scene.index,
    };
  }
  
  function isSceneNotStale(scene: NavigationScene): boolean {
    return !scene.isStale;
  }
  
  function isSceneActive(scene: NavigationScene): boolean {
    return scene.isActive;
  }
  
  const styles = StyleSheet.create({
    main: {
      flex: 1,
    },
  });
  
  export default Transitioner;
  