import * as React from "react";
import * as R from "ramda";
import { FlatList, StyleSheet, View } from "react-native";
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
import { RiverItem } from "../RiverItem";
import { RiverFooter } from "../RiverFooter";
import { RiverError } from "../RiverError";
import { useScreenConfiguration } from "../useScreenConfiguration";
import { RefreshControl } from "../RefreshControl";
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
import {
  usePipesCacheReset,
  useProfilerLogging,
} from "@applicaster/zapp-react-native-utils/reactHooks";
import { useLoadingState } from "./hooks/useLoadingState";
import { ViewportTracker } from "../../Viewport";
import {
  ScreenLoadingMeasurements,
  ScreenLoadingMeasurementsListItemWrapper,
} from "@applicaster/zapp-react-native-utils/riverComponetsMeasurementProvider";
import { useScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData";
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
import { useShallow } from "zustand/react/shallow";
import { emitScrollEndReached } from "@applicaster/zapp-react-native-ui-components/events";

import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
import { ComponentsMapHeightContext } from "./ContextProviders/ComponentsMapHeightContext";
import { ComponentsMapRefContext } from "./ContextProviders/ComponentsMapRefContext";

const isAndroid = isAndroidPlatform();

type Props = {
  feed: ZappFeed;
  groupId?: string;
  isScreenWrappedInContainer?: boolean;
  riverComponents: ZappUIComponent[];
  scrollViewExtraProps?: {};
  riverId?: string;
  getStaticComponentFeed: any;
  pullToRefreshPipesV1RefreshingStateUpdater: () => boolean;
  refreshingPipesV1?: boolean;
  stickyHeaderIndices?: number[];
};

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

const getNearestValue = (value, optionA, optionB) =>
  Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;

const scrollIndicatorInsets = { right: 1 };
const keyExtractor = R.compose(String, R.prop("id"));

function ComponentsMapComponent(props: Props) {
  const {
    riverComponents: components,
    scrollViewExtraProps,
    isScreenWrappedInContainer,
    feed,
    groupId,
    riverId,
    getStaticComponentFeed,
    // Method added to keep pipes v1 logic up to date with the pullToRefresh state.
    // TODO: Remove when pipes v1 is deprecated.
    pullToRefreshPipesV1RefreshingStateUpdater,
    refreshingPipesV1,
    stickyHeaderIndices,
  } = props;

  const flatListRef = React.useRef<FlatList | null>(null);
  const flatListWrapperRef = React.useRef<View | null>(null);
  const hasUserScrolledRef = React.useRef(false);
  const screenConfig = useScreenConfiguration(riverId);
  const screenData = useScreenData(riverId);
  const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;

  const [flatListHeight, setFlatListHeight] = React.useState(null);

  const riverComponents = React.useMemo(() => {
    if (feed?.entry?.length < components.length) {
      return R.slice(0, feed?.entry?.length, components);
    }

    return components;
  }, [components, feed]);

  const logTimestamp = useProfilerLogging();

  const onLoadDone = React.useCallback(() => {
    logTimestamp(riverId?.toString());
  }, [logTimestamp]);

  const { loadingState, onLoadFinished, onLoadFailed, shouldShowLoadingError } =
    useLoadingState(riverComponents.length, onLoadDone);

  const theme = useTheme();

  const renderRiverItem = React.useCallback(
    ({ item, index }) => {
      const riverItemProps = {
        riverId,
        item,
        index,
        isScreenWrappedInContainer,
        feed,
        groupId,
        onLoadFailed,
        onLoadFinished,
        getStaticComponentFeed,
        isLast: isLast(index, riverComponents.length),
        loadingState,
      };

      return (
        <ScreenLoadingMeasurementsListItemWrapper index={index}>
          <RiverItem {...riverItemProps} />
        </ScreenLoadingMeasurementsListItemWrapper>
      );
    },
    [feed, getStaticComponentFeed, onLoadFailed, onLoadFinished]
  );

  const screenStyle = React.useMemo(
    () => ({
      paddingTop: ifEmptyUseFallback(
        screenConfig.paddingTop,
        R.prop("screen_padding_top")(theme)
      ),
      paddingBottom: ifEmptyUseFallback(
        screenConfig.paddingBottom,
        R.prop("screen_padding_bottom")(theme)
      ),
      paddingLeft: ifEmptyUseFallback(
        screenConfig.paddingLeft,
        R.prop("screen_padding_left")(theme)
      ),
      paddingRight: ifEmptyUseFallback(
        screenConfig.paddingRight,
        R.prop("screen_padding_right")(theme)
      ),
    }),
    [riverId]
  );

  const handleOnLayout = React.useCallback(
    ({
      nativeEvent: {
        layout: { height },
      },
    }) => {
      if (!flatListHeight) {
        setFlatListHeight(height);
      }
    },
    [flatListHeight]
  );

  usePipesCacheReset(riverId, riverComponents);

  const refreshControl = React.useMemo(
    () =>
      pullToRefreshEnabled ? (
        <RefreshControl
          pullToRefreshPipesV1RefreshingStateUpdater={
            pullToRefreshPipesV1RefreshingStateUpdater
          }
          refreshingPipesV1={refreshingPipesV1}
        />
      ) : null,
    []
  );

  const navBarStore = useScreenContextV2()._navBarStore;

  const scrollY = navBarStore(
    useShallow(({ scrollYAnimated }) => scrollYAnimated)
  );

  const canMomentum = React.useRef(false);

  const onScrollBeginDrag = React.useCallback(() => {
    canMomentum.current = true;
  }, []);

  /* onMomentumScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
  const _onMomentumScrollEnd = React.useCallback(({ nativeEvent }) => {
    if (!canMomentum.current) return;
    canMomentum.current = false;

    const { height: headerHeight, scrollState } = navBarStore.getState();

    const offsetY = Math.max(0, nativeEvent.contentOffset.y);

    if (
      !(scrollState === 0 || scrollState === -headerHeight) &&
      flatListRef.current
    ) {
      const offset =
        getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
        -headerHeight
          ? Math.round(offsetY + scrollState + headerHeight)
          : Math.round(offsetY + scrollState);

      flatListRef.current.scrollToOffset({
        animated: true,
        offset,
      });
    }
  }, []);

  /* onScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
  const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
    const { height: headerHeight, scrollState } = navBarStore.getState();

    const offsetY = Math.max(0, nativeEvent.contentOffset.y);

    if (
      !(scrollState === 0 || scrollState === -headerHeight) &&
      flatListRef.current &&
      nativeEvent.velocity.y === 0
    ) {
      const offset =
        getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
        -headerHeight
          ? Math.round(offsetY + scrollState + headerHeight)
          : Math.round(offsetY + scrollState);

      flatListRef.current.scrollToOffset({
        animated: true,
        offset,
      });
    }
  }, []);

  const onScroll = React.useCallback((event) => {
    hasUserScrolledRef.current = true;

    const {
      nativeEvent: {
        contentOffset: { y },
        layoutMeasurement: { height },
        contentSize: { height: contentHeight },
      },
    } = event;

    if (y >= 0 && y + height <= contentHeight) {
      scrollY.setValue(y);
    } else {
      if (y <= 0) {
        scrollY.setValue(0);
      } else if (y + height > contentHeight) {
        scrollY.setValue(contentHeight - height);
      }
    }
  }, []);

  const contentContainerStyle = React.useMemo(
    () => (isScreenWrappedInContainer ? {} : screenStyle),
    [isScreenWrappedInContainer, screenStyle]
  );

  if (shouldShowLoadingError) {
    return <RiverError />;
  }

  // TODO: support both "isScreenWrappedInContainer" and hasScreenPicker().
  // The Screen Picker in Mobile is completly different than the TV
  // so the various offsets / margins in TV do not apply here.
  return (
    <View style={styles.container} ref={flatListWrapperRef}>
      <ComponentsMapHeightContext.Provider value={flatListHeight}>
        <ComponentsMapRefContext.Provider value={flatListWrapperRef}>
          <ScreenLoadingMeasurements
            riverId={riverId}
            numberOfComponents={riverComponents.length}
          >
            <ViewportTracker>
              <FlatList
                ref={(ref) => {
                  flatListRef.current = ref;
                }}
                // Fix for WebView rerender crashes on Android API 28+
                // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
                overScrollMode={isAndroid ? "never" : "auto"}
                scrollIndicatorInsets={scrollIndicatorInsets}
                extraData={feed}
                stickyHeaderIndices={stickyHeaderIndices}
                removeClippedSubviews={isAndroid}
                onLayout={handleOnLayout}
                initialNumToRender={3}
                maxToRenderPerBatch={10}
                windowSize={12}
                keyExtractor={keyExtractor}
                renderItem={renderRiverItem}
                data={riverComponents}
                contentContainerStyle={contentContainerStyle}
                ListFooterComponent={
                  <RiverFooter
                    flatListHeight={flatListHeight}
                    loadingState={loadingState}
                  />
                }
                refreshControl={refreshControl}
                onScrollBeginDrag={onScrollBeginDrag}
                onScroll={onScroll}
                onMomentumScrollEnd={_onMomentumScrollEnd}
                onScrollEndDrag={_onScrollEndDrag}
                scrollEventThrottle={16}
                {...scrollViewExtraProps}
                /* When wrapped in a parent ScrollView (e.g. tabs),
                this FlatList doesn't scroll so onEndReached can fire repeatedly;
                skip it here and let the parent ScrollView emit scroll-end instead. */
                onEndReached={
                  isScreenWrappedInContainer
                    ? undefined
                    : () => {
                        if (!hasUserScrolledRef.current) return;
                        emitScrollEndReached();
                      }
                }
              />
            </ViewportTracker>
          </ScreenLoadingMeasurements>
        </ComponentsMapRefContext.Provider>
      </ComponentsMapHeightContext.Provider>
    </View>
  );
}

export const ComponentsMap = React.memo(
  withComponentsMapProvider(ComponentsMapComponent),
  R.equals
);
