import React, { Component } from 'react';
import {
  RefreshControl,
  Platform,
  ScrollView,
  Animated,
  Dimensions,
  LayoutChangeEvent,
  NativeSyntheticEvent,
  NativeScrollEvent,
} from 'react-native';
/*
 * https://reactnavigation.org/docs/en/scrollables.html
 * It will scroll to top if you tap the active tab
 * */
// import {
//   //   NavigationEventSubscription,
//   //   NavigationScreenProp,
//   // ScrollView,
//   //   withNavigation,
// } from 'react-navigation';
import { filter, tap } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-native-safe-area-context';
import { SBox } from '../s-components/layout/s-box';
import { RefreshIndicator } from './refresh-indicator';
import { theme } from '../../app/theme';
import {
  scrollableListSubject$,
  scrollNamedSubject$,
  setScrollState,
} from '../utils/scroll-service';
import { Toaster } from '../submodules/toaster/components/toaster';
// TODO move it out of there after finding better solution to share common push toaster between pages
import {
  detectIosDeviceSafeArea,
  isCloseToBottom,
  isLostFocus,
} from '../transducers';
import { fp as _ } from '../utils/fp';
import { ContextVirtualization } from '../context';
import { userMarket$ } from '../utils/market';
import { CustomedStatusBar } from './customed-status-bar';
import { TColor, TRefreshableRenderer } from '../types';
import { TAppPage } from '../../app/types';
import { ScrollViewLock } from '../utils/animation-service';
import { RefreshService } from '../utils/refresh-service';
import { withNavigation } from '../hoc/with-navigation';
import { navigationBetweenScreens$ } from '../utils/navigation-service';

const { width, height } = Dimensions.get('window');

const reactotronScroller$ =
  // @ts-ignore
  global.__DEV__ && process.env.ENV !== 'test'
    ? require('../utils/debuggers/reactotron-config').reactotronScrollSubject$
    : undefined;

type TProps = {
  // navigation: NavigationScreenProp<any>;
  name: TAppPage;
  withoutBottomTabNavigator?: boolean;
  scrollTop?: number;
  renderBgLayer?: TRefreshableRenderer;
  renderBgLayer2?: TRefreshableRenderer;
  renderBody?: TRefreshableRenderer;
  renderFgLayer?: TRefreshableRenderer;
  renderFgLayer2?: TRefreshableRenderer;
  onPullDown?: (value?: boolean) => void;
  loading?: boolean;
  renderHeader?: TRefreshableRenderer;
  indicatorLight?: boolean;
  indicatorOffsetTop?: number;
  // onMomentumScrollEnd: PropTypes.func,
  onScrollReachBottom?: (e: NativeSyntheticEvent<NativeScrollEvent>) => void;
  // enableScroll?: boolean;
  onScrollEndDrag?: (
    e: NativeSyntheticEvent<NativeScrollEvent>,
    callback: (y: number, animated?: boolean) => void
  ) => void;
  bgColor?: TColor;
  statusBarLight?: boolean;
  statusBarToggleByScroll?: boolean;
  withNativeDriver?: boolean;
  // testID?: string;
  route: {
    name: string;
    params: any;
  };
};

type TState = {
  scrollY: Animated.Value;
  isScrolled: boolean;
  enableScroll: boolean;
};

/*
 * Custom loader
 * http://qaru.site/questions/456933/how-to-customize-lookfeel-of-react-native-listviews-refreshcontrol
 * https://medium.com/@lennyboyatzis/custom-pull-to-refresh-animations-in-react-native-1efac58609d3
 * */
export class RefreshableComponent extends Component<TProps, TState> {
  state = {
    scrollY: new Animated.Value(0),
    isScrolled: false,
    enableScroll: true,
  };

  scrollView: ScrollView | null = null;

  yOffset = 0;

  marketSubscription?: Subscription;

  scrollLockSub?: Subscription;

  didBlurSubscription?: Subscription;

  hasSafeArea?: boolean;

  reactotronSubscription?: Subscription;

  contentHeight?: number;

  maxOffset?: number;

  viewportHeight?: number;

  scrollWatcher?: Subscription;

  static defaultProps = {
    loading: false,
    renderBgLayer: () => {},
    renderBgLayer2: () => {},
    renderFgLayer: () => {},
    renderFgLayer2: () => {},
    indicatorLight: false,
    indicatorOffsetTop: undefined,
    onScrollReachBottom: () => {},
    onScrollEndDrag: () => {},
    // enableScroll: true,
    onPullDown: undefined,
    scrollTop: 0,
    bgColor: 'ebonyClay' as TColor,
    statusBarLight: true,
    statusBarToggleByScroll: false,
  };

  componentDidMount() {
    // StatusBar.setHidden(true);
    const { name, withoutBottomTabNavigator, route } = this.props;

    this.marketSubscription = userMarket$
      .pipe(
        tap(() => {
          this.scrollToTop(true);
          this.setState({ isScrolled: false } as TState);
        })
      )
      .subscribe();

    if (name) {
      this.didBlurSubscription = navigationBetweenScreens$
        .pipe(filter(isLostFocus(_.path('name', route))))
        .subscribe(() => {
          this.scrollToTop(true);
        });
    }

    if (withoutBottomTabNavigator) {
      this.hasSafeArea =
        Platform.OS === 'ios' ? detectIosDeviceSafeArea() : false;
    }

    if (reactotronScroller$) {
      this.reactotronSubscription = reactotronScroller$.subscribe(
        this.scrollTo
      );
    }

    this.monitorScrollEnable();
    this.monitorScrollLockEnable();
  }

  componentWillUnmount() {
    if (this.didBlurSubscription) {
      this.didBlurSubscription.unsubscribe();
    }

    if (reactotronScroller$) {
      this.reactotronSubscription!.unsubscribe();
    }

    if (this.marketSubscription) {
      this.marketSubscription.unsubscribe();
    }

    this.monitorScrollDisable();
    this.monitorScrollLockDisable();
  }

  onContentSizeChange = (contentWidth: number, contentHeight: number) => {
    this.contentHeight = contentHeight;

    this.maxOffset = this.contentHeight - this.viewportHeight!;

    if (this.maxOffset < this.yOffset) {
      this.yOffset = this.maxOffset;
    }

    if (this.contentHeight === this.viewportHeight) {
      this.scrollToTop(true);
    }
    this.setScrollState();
  };

  onLayout = (event: LayoutChangeEvent) => {
    this.viewportHeight = event.nativeEvent.layout.height;

    this.maxOffset = this.contentHeight! - this.viewportHeight!;
    if (this.maxOffset < this.yOffset) {
      this.yOffset = this.maxOffset;
    }
    this.setScrollState();
  };

  onRefresh = () => {
    const { onPullDown, route } = this.props;
    const enableRefresh = _.isNotNil(onPullDown);

    if (enableRefresh && onPullDown) {
      onPullDown(true);
      scrollableListSubject$.next();
      RefreshService.next(route.name);
    }
  };

  setScrollState = () => {
    const { name } = this.props;
    const remainOffset = this.maxOffset! - this.yOffset;
    // TODO is it used anywhere???
    // TODO is it used anywhere???
    // TODO is it used anywhere???
    setScrollState(name, {
      yOffset: this.yOffset,
      maxOffset: this.maxOffset!,
      remainOffset,
      viewportHeight: this.viewportHeight!,
      contentHeight: this.contentHeight!,
    });
  };

  scrollTo = (y: number, animated?: boolean) => {
    this.scrollView!.scrollTo({
      x: 0,
      y: y || 0,
      animated: animated || false,
    });
  };

  scrollToTop = (withoutAnimation?: boolean) => {
    if (this.scrollView && this.scrollView.scrollTo) {
      this.scrollView.scrollTo({
        x: 0,
        y: 0,
        animated: !withoutAnimation,
      });
    }
  };

  scrollToBottom = (withoutAnimation?: boolean) => {
    this.scrollView!.scrollToEnd({ animated: !withoutAnimation });
  };

  monitorScrollEnable = () => {
    const { name, scrollTop } = this.props;
    this.scrollWatcher = scrollNamedSubject$
      .pipe(
        filter(
          _.compose(
            _.eq(name),
            _.path('name')
          )
        )
      )
      .subscribe(e => {
        const { isScrolled } = this.state;
        const { y } = e.nativeEvent.contentOffset;
        let nextIsScrolled;

        nextIsScrolled = y > scrollTop!;
        if (nextIsScrolled !== isScrolled) {
          this.setState({ isScrolled: nextIsScrolled } as TState);
        }
      });
  };

  monitorScrollDisable = () => {
    if (this.scrollWatcher) {
      this.scrollWatcher.unsubscribe();
    }
  };

  monitorScrollReachBottom = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
    const { onScrollReachBottom } = this.props;
    if (isCloseToBottom(e.nativeEvent) && onScrollReachBottom) {
      onScrollReachBottom(e);
    }
  };

  monitorScrollLockEnable = () => {
    let state = false;
    this.scrollLockSub = ScrollViewLock.get().subscribe(val => {
      if (val !== state) {
        state = val;
        if (!val) {
          this.enableScroll();
        } else {
          this.disableScroll();
        }
      }
    });
  };

  monitorScrollLockDisable = () => {
    if (this.scrollLockSub) {
      this.scrollLockSub.unsubscribe();
    }
  };

  enableScroll = () => {
    // const { withNativeDriver } = this.props;
    // if (!withNativeDriver) {
    const { enableScroll } = this.state;
    if (!enableScroll) {
      this.setState({ enableScroll: true } as TState);
    }

    //   return;
    // }

    if (this.scrollView) {
      this.scrollView!.setNativeProps({ scrollEnabled: true });
    }
  };

  disableScroll = () => {
    // const { withNativeDriver } = this.props;
    // if (!withNativeDriver) {
    const { enableScroll } = this.state;
    if (enableScroll) {
      this.setState({ enableScroll: false } as TState);
    }
    //   return;
    // }
    if (this.scrollView) {
      this.scrollView!.setNativeProps({ scrollEnabled: false });
    }
  };

  render() {
    const {
      onPullDown,
      loading,
      renderHeader,
      renderBody,
      renderBgLayer,
      renderBgLayer2,
      indicatorLight,
      indicatorOffsetTop,
      // hasSafeArea,
      renderFgLayer,
      renderFgLayer2,
      onScrollEndDrag,
      name,
      bgColor,
      statusBarLight,
      statusBarToggleByScroll,
      withNativeDriver,
    } = this.props;

    const { scrollY, isScrolled, enableScroll } = this.state;
    const isIos = Platform.OS === 'ios';
    const revertStatusBarStyle = statusBarToggleByScroll ? isScrolled : false;
    const renderProps = {
      animationY: scrollY,
      scrollToTop: this.scrollToTop,
      scrollTo: this.scrollTo,
      scrollToBottom: this.scrollToBottom,
      isScrolled,
    };

    const onScroll = Animated.event(
      [
        {
          nativeEvent: { contentOffset: { y: scrollY } },
        },
      ],
      {
        // Optional async listener
        // https://animationbook.codedaily.io/animated-event/
        listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
          this.yOffset = e.nativeEvent.contentOffset.y;
          scrollNamedSubject$.next({ name, nativeEvent: e.nativeEvent });
          // TODO: is it still used???
          // TODO: is it still used???
          // TODO: is it still used???
          this.setScrollState();
        },
        useNativeDriver: !!withNativeDriver,
      }
    );

    const enableRefresh = _.isNotNil(onPullDown);

    const content = (
      <GestureHandlerRootView
        style={[
          {
            flex: 1,
          },
        ]}
      >
        <SBox backgroundColor={bgColor} flex={1}>
          <CustomedStatusBar
            defaultLightContent={statusBarLight}
            isReverted={revertStatusBarStyle}
          />
          {withNativeDriver ? (
            renderHeader ? (
              renderHeader(renderProps)
            ) : (
              <SBox />
            )
          ) : (
            <SBox position="absolute" top={0} zIndex={1} width={1}>
              {renderHeader ? renderHeader(renderProps) : <SBox />}
            </SBox>
          )}

          <SBox flex={1} width={width} height={height} position="absolute">
            {renderBgLayer!(renderProps)}
          </SBox>
          <SBox flex={1} width={width} height={height} position="absolute">
            {renderBgLayer2!(renderProps)}
          </SBox>
          <Toaster hasSafeArea={this.hasSafeArea} page={name} />
          <SBox flex={1}>
            {withNativeDriver ? (
              <Animated.ScrollView
                testID="animated-scrollview"
                // https://stackoverflow.com/a/47984187/2614096
                onLayout={this.onLayout}
                onContentSizeChange={this.onContentSizeChange}
                // bounces={false}
                onScroll={onScroll}
                ref={(ref: any) => {
                  this.scrollView = ref as ScrollView;
                }}
                scrollEventThrottle={16}
                disableScrollViewPanResponder={!enableScroll}
                nestedScrollEnabled
                // scrollEventThrottle={1}
                // stickyHeaderIndices={[0]}
                // scrollEnabled={enableScroll}
                onScrollEndDrag={(
                  e: NativeSyntheticEvent<NativeScrollEvent>
                ) => {
                  onScrollEndDrag!(e, this.scrollTo);
                  this.monitorScrollReachBottom(e);
                }}
                contentContainerStyle={{
                  // https://github.com/facebook/react-native/issues/4099#issuecomment-307541206
                  flexGrow: 1,
                }}
                scrollIndicatorInsets={{
                  // https://github.com/facebook/react-native/issues/26610#issuecomment-553961114
                  right: 1,
                }}
                refreshControl={
                  <RefreshControl // all properties must be transparent
                    style={{
                      opacity: Platform.OS === 'android' ? 1 : 0,
                    }}
                    enabled={enableRefresh}
                    tintColor="transparent"
                    colors={[theme.colors.wildStrawberry]}
                    refreshing={loading!}
                    onRefresh={this.onRefresh}
                  />
                }
                // https://stackoverflow.com/a/50510934/6190198
                keyboardShouldPersistTaps="handled"
                // fixing bag with disabling page after long press on text input
                keyboardDismissMode="on-drag"
                removeClippedSubviews={false}
                onMomentumScrollEnd={(
                  e: NativeSyntheticEvent<NativeScrollEvent>
                ) => {
                  this.monitorScrollReachBottom(e);
                }}
                style={{ position: 'relative' }}
                bounces={enableRefresh}
              >
                {isIos && loading ? (
                  <RefreshIndicator
                    indicatorOffsetTop={indicatorOffsetTop}
                    indicatorLight={indicatorLight}
                  />
                ) : null}
                <ContextVirtualization.Provider
                  value={{
                    parentScreen: name,
                  }}
                >
                  {renderBody!(renderProps)}
                </ContextVirtualization.Provider>
              </Animated.ScrollView>
            ) : (
              <ScrollView
                testID="scrollview"
                // https://stackoverflow.com/a/47984187/2614096
                onLayout={this.onLayout}
                onContentSizeChange={this.onContentSizeChange}
                // bounces={false}
                onScroll={onScroll}
                scrollEventThrottle={16}
                ref={ref => {
                  this.scrollView = ref;
                }}
                disableScrollViewPanResponder={!enableScroll}
                nestedScrollEnabled
                // scrollEventThrottle={1}
                // stickyHeaderIndices={[0]}
                scrollEnabled={enableScroll}
                onScrollEndDrag={(
                  e: NativeSyntheticEvent<NativeScrollEvent>
                ) => {
                  onScrollEndDrag!(e, this.scrollTo);
                  this.monitorScrollReachBottom(e);
                }}
                contentContainerStyle={{
                  // https://github.com/facebook/react-native/issues/4099#issuecomment-307541206
                  flexGrow: 1,
                }}
                scrollIndicatorInsets={{
                  // https://github.com/facebook/react-native/issues/26610#issuecomment-553961114
                  right: 1,
                }}
                refreshControl={
                  <RefreshControl // all properties must be transparent
                    style={{
                      opacity: Platform.OS === 'android' ? 1 : 0,
                    }}
                    enabled={enableRefresh}
                    tintColor="transparent"
                    colors={[theme.colors.wildStrawberry]}
                    refreshing={loading!}
                    onRefresh={this.onRefresh}
                  />
                }
                // https://stackoverflow.com/a/50510934/6190198
                keyboardShouldPersistTaps="handled"
                // fixing bag with disabling page after long press on text input
                keyboardDismissMode="on-drag"
                removeClippedSubviews={false}
                onMomentumScrollEnd={(
                  e: NativeSyntheticEvent<NativeScrollEvent>
                ) => {
                  this.monitorScrollReachBottom(e);
                }}
                style={{ position: 'relative' }}
                bounces={enableRefresh}
              >
                {isIos && loading ? (
                  <RefreshIndicator
                    indicatorOffsetTop={indicatorOffsetTop}
                    indicatorLight={indicatorLight}
                  />
                ) : null}
                <ContextVirtualization.Provider
                  value={{
                    parentScreen: name,
                  }}
                >
                  {renderBody!(renderProps)}
                </ContextVirtualization.Provider>
              </ScrollView>
            )}
          </SBox>
          {renderFgLayer!(renderProps)}
          {renderFgLayer2!(renderProps)}
        </SBox>
      </GestureHandlerRootView>
    );

    return Platform.OS === 'android' ? (
      <SafeAreaView style={{ flex: 1 }}>{content}</SafeAreaView>
    ) : (
      content
    );
  }
}

export const RefreshableView = withNavigation(RefreshableComponent);
