import React, { PureComponent } from 'react';
import { Animated, Dimensions, ScrollView } from 'react-native';
import { Nullable } from 'tsdef';
import { Touchable } from '../touchable';
import { fp as _ } from '../../utils/fp';
import { getDotsRange, getAnimation } from '../../transducers';
import { TScrollToItem } from '../../types';
import { theme } from '../../../app/theme';
import { SCarouselDot } from '../../s-components/s-carousel-dot';
import { SRow } from '../../s-components/layout/s-row';
import { SBox } from '../../s-components/layout/s-box';
import { SBoxAnimated } from '../../s-components/layout/s-box-animated';
import { SCarouselButton } from '../../s-components/s-carousel-button';

const { width: DEVICE_WIDTH } = Dimensions.get('window');
const ROUNDED_WIDTH = Math.floor(DEVICE_WIDTH);

type TProps = {
  data: any[];
  renderItem: (
    item: any,
    i?: number,
    slideHeight?: Nullable<number>
  ) => React.ReactElement;
  keyExtractor: (item: any) => number | string;
  // initialScrollIndex?: number;
  onEndReached?: () => void;
  withButtons?: boolean;
  withDots?: boolean;
  itemHeight?: number;
  itemWidth?: number;
  dotsGap?: number;
  renderNextButton?: () => React.ReactElement;
  renderPrevButton?: () => React.ReactElement;
  disabled?: boolean;
  slideFullwidth?: boolean;
  minHeight?: number;
  externalIndex?: number;
  onScrollCallback?: (index: number) => void;
};

type TState = {
  currentStepIndex: number;
  carouselHeight: Nullable<number>;
};

export class Carousel extends PureComponent<TProps, TState> {
  state = {
    currentStepIndex: 0,
    carouselHeight: null,
  };

  scrollRef: ScrollView | null = null;

  scrollX = new Animated.Value(0);

  static defaultProps = {
    // initialScrollIndex: 0,
    withButtons: false,
    withDots: false,
    dotsGap: 0,
    disabled: false,
    slideFullwidth: true,
    minHeight: 'auto',
  };

  componentDidUpdate({ externalIndex: oldExternalIndex }: TProps) {
    const { externalIndex } = this.props;
    if (externalIndex !== oldExternalIndex && _.isNotNil(externalIndex)) {
      this.goToItem(externalIndex!);
    }
  }

  init = (height: number) => {
    this.setState({ carouselHeight: height });
  };

  handleScroll = (nextStepIndex: number) => {
    this.setState({ currentStepIndex: nextStepIndex });
  };

  scrollToItem: TScrollToItem = ({ stepNumber, withAnimation }) => {
    if (this.scrollRef && this.scrollRef) {
      this.scrollRef.scrollTo({
        x: stepNumber * this.getItemWidth(),
        y: 0,
        animated: withAnimation,
      });
      this.setState({ currentStepIndex: stepNumber });
    }
  };

  getItemWidth = () => {
    const { itemWidth } = this.props;
    return itemWidth! || ROUNDED_WIDTH;
  };

  getDotsAnimations = (index: number) => {
    const itemWidth = this.getItemWidth();
    const inputRange = getDotsRange(index, itemWidth);
    return getAnimation({
      opacity: {
        inputRange,
        outputRange: [0, 1, 0],
      },
    })(this.scrollX);
  };

  goToNextItem = () => {
    const { onEndReached, data } = this.props;
    const { currentStepIndex } = this.state;
    const LENGTH = _.length(data);

    if (currentStepIndex === LENGTH - 1) {
      if (onEndReached) {
        onEndReached();
      }
      return;
    }

    if (currentStepIndex < LENGTH - 1) {
      this.goToItem(currentStepIndex + 1);
    }
  };

  goToPrevItem = () => {
    const { currentStepIndex } = this.state;

    if (currentStepIndex > 0) {
      this.goToItem(currentStepIndex - 1);
    }
  };

  goToItem = (index: number) => {
    this.setState({ currentStepIndex: index }, () => {
      this.scrollToItem({
        stepNumber: index,
        withAnimation: true,
      });
    });
  };

  renderDots = () => {
    const { data, keyExtractor } = this.props;

    if (data.length < 2) return null;

    return data.map((el, i) => {
      return (
        <SBox position="relative" key={keyExtractor(el)}>
          <SBoxAnimated position="absolute" style={[this.getDotsAnimations(i)]}>
            <SCarouselDot bg={theme.colors.wildStrawberry} />
          </SBoxAnimated>
          <SCarouselDot bg={theme.colors.eastBay} />
        </SBox>
      );
    });
  };

  renderItems = () => {
    const {
      data,
      renderItem,
      keyExtractor,
      itemHeight,
      slideFullwidth,
      minHeight,
    } = this.props;

    const { carouselHeight } = this.state;
    const slideHeight = itemHeight || carouselHeight;

    return data.map((el, i) => {
      return (
        <SBoxAnimated
          flex={1}
          width={slideFullwidth ? this.getItemWidth() : 1}
          height={slideHeight}
          key={keyExtractor(el)}
          minHeight={minHeight}
        >
          {renderItem(el, i, slideHeight)}
        </SBoxAnimated>
      );
    });
  };

  render() {
    const {
      withDots,
      withButtons,
      renderNextButton,
      renderPrevButton,
      dotsGap,
      data,
      disabled,
      onScrollCallback,
    } = this.props;
    const galleryGap = data.length < 2 ? -12 : 20;
    return (
      <SBox position="relative" mb={galleryGap}>
        <Animated.ScrollView
          scrollEnabled={!disabled}
          horizontal
          pagingEnabled
          // scroll by several slides per drug on android
          // snapToAlignment="start"
          // scroll by several slides per drug
          // snapToInterval={this.getItemWidth()}
          ref={(ref: any) => {
            this.scrollRef = ref as ScrollView;
          }}
          showsHorizontalScrollIndicator={false}
          onScroll={Animated.event(
            [
              {
                nativeEvent: { contentOffset: { x: this.scrollX } },
              },
            ],
            { useNativeDriver: true }
          )}
          onMomentumScrollEnd={e => {
            const nextIndex = Math.round(
              e.nativeEvent.contentOffset.x / this.getItemWidth()
            );
            if (_.isFunction(onScrollCallback)) {
              onScrollCallback(nextIndex);
            }
          }}
          scrollEventThrottle={16}
          // contentContainerStyle={{ height: this.state.carouselHeight }}
          onContentSizeChange={(w, h) => {
            if (h) {
              this.init(h);
            }
          }}
        >
          {this.renderItems()}
        </Animated.ScrollView>
        {withDots ? (
          <SRow justifyContent="center" mt={dotsGap}>
            {this.renderDots()}
          </SRow>
        ) : null}
        {withButtons ? (
          <>
            <Touchable testID="carousel-previous" onPress={this.goToPrevItem}>
              <SCarouselButton>{renderPrevButton!()}</SCarouselButton>
            </Touchable>
            <Touchable testID="carousel-next" onPress={this.goToNextItem}>
              <SCarouselButton rightPosition>
                {renderNextButton!()}
              </SCarouselButton>
            </Touchable>
          </>
        ) : null}
      </SBox>
    );
  }
}
