/* @flow */

import * as React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, StyleSheet } from 'react-native';
import { PagerRendererPropType } from './PropTypes';
import type { PagerRendererProps } from './TypeDefinitions';

type ScrollEvent = {
  nativeEvent: {
    contentOffset: {
      x: number,
      y: number,
    },
    contentSize: {
      height: number,
      width: number,
    },
  },
};

type State = {|
  initialOffset: {| x: number, y: number |},
|};

type Props<T> = PagerRendererProps<T> & {
  bounces: boolean,
};

export default class PagerScroll<T: *> extends React.Component<
  Props<T>,
  State
> {
  static propTypes = {
    ...PagerRendererPropType,
    bounces: PropTypes.bool.isRequired,
  };

  static defaultProps = {
    canJumpToTab: () => true,
    bounces: false,
  };

  constructor(props: Props<T>) {
    super(props);

    const { navigationState, layout } = this.props;

    this.state = {
      initialOffset: {
        x: navigationState.index * layout.width,
        y: 0,
      },
    };
  }

  componentDidMount() {
    this._setInitialPage();
  }

  componentDidUpdate(prevProps: Props<T>) {
    const amount = this.props.navigationState.index * this.props.layout.width;

    if (
      prevProps.navigationState.routes.length !==
        this.props.navigationState.routes.length ||
      prevProps.layout.width !== this.props.layout.width
    ) {
      this._scrollTo(amount, false);
    } else if (
      prevProps.navigationState.index !== this.props.navigationState.index
    ) {
      this._scrollTo(amount);
    }
  }

  _scrollView: ?ScrollView;
  _idleCallback: any;
  _isIdle: boolean = true;
  _isInitial: boolean = true;

  _setInitialPage = () => {
    if (this.props.layout.width) {
      this._isInitial = true;
      this._scrollTo(
        this.props.navigationState.index * this.props.layout.width,
        false
      );
    }

    setTimeout(() => {
      this._isInitial = false;
    }, 50);
  };

  _scrollTo = (x: number, animated = true) => {
    if (this._isIdle && this._scrollView) {
      this._scrollView.scrollTo({
        x,
        animated: animated && this.props.animationEnabled !== false,
      });
    }
  };

  _handleMomentumScrollEnd = (e: ScrollEvent) => {
    let nextIndex = Math.round(
      e.nativeEvent.contentOffset.x / this.props.layout.width
    );

    const nextRoute = this.props.navigationState.routes[nextIndex];

    if (this.props.canJumpToTab({ route: nextRoute })) {
      this.props.jumpTo(nextRoute.key);
      this.props.onAnimationEnd && this.props.onAnimationEnd();
    } else {
      global.requestAnimationFrame(() => {
        this._scrollTo(
          this.props.navigationState.index * this.props.layout.width
        );
      });
    }
  };

  _handleScroll = (e: ScrollEvent) => {
    if (this._isInitial || e.nativeEvent.contentSize.width === 0) {
      return;
    }

    const { navigationState, layout } = this.props;
    const offset = navigationState.index * layout.width;

    this.props.offsetX.setValue(-offset);
    this.props.panX.setValue(offset - e.nativeEvent.contentOffset.x);

    global.cancelAnimationFrame(this._idleCallback);

    this._isIdle = false;
    this._idleCallback = global.requestAnimationFrame(() => {
      this._isIdle = true;
    });
  };

  render() {
    const {
      children,
      layout,
      navigationState,
      onSwipeStart,
      onSwipeEnd,
      bounces,
    } = this.props;

    return (
      <ScrollView
        horizontal
        pagingEnabled
        directionalLockEnabled
        keyboardDismissMode="on-drag"
        keyboardShouldPersistTaps="always"
        overScrollMode="never"
        scrollToOverflowEnabled
        scrollEnabled={this.props.swipeEnabled}
        automaticallyAdjustContentInsets={false}
        bounces={bounces}
        alwaysBounceHorizontal={bounces}
        scrollsToTop={false}
        showsHorizontalScrollIndicator={false}
        scrollEventThrottle={1}
        onScroll={this._handleScroll}
        onScrollBeginDrag={onSwipeStart}
        onScrollEndDrag={onSwipeEnd}
        onMomentumScrollEnd={this._handleMomentumScrollEnd}
        contentOffset={this.state.initialOffset}
        style={styles.container}
        contentContainerStyle={layout.width ? null : styles.container}
        ref={el => (this._scrollView = el)}
      >
        {React.Children.map(children, (child, i) => {
          const route = navigationState.routes[i];
          const focused = i === navigationState.index;

          return (
            <View
              key={route.key}
              testID={this.props.getTestID({ route })}
              accessibilityElementsHidden={!focused}
              importantForAccessibility={
                focused ? 'auto' : 'no-hide-descendants'
              }
              style={
                layout.width
                  ? { width: layout.width, overflow: 'hidden' }
                  : focused
                    ? styles.page
                    : null
              }
            >
              {focused || layout.width ? child : null}
            </View>
          );
        })}
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  page: {
    flex: 1,
    overflow: 'hidden',
  },
});