import * as React from "react";
import * as R from "ramda";
import { View, StyleSheet, Platform, FlatList } 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 {
  useProfilerLogging,
  usePipesCacheReset,
} 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";

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

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,
  } = props;

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

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

  const {
    isAnyLoading,
    isAllLoaded,
    waitForAllComponents,
    onLoadFinished,
    onLoadFailed,
    shouldShowLoadingError,
    isAnyLoaded,
    arePreviousComponentsLoaded,
  } = useLoadingState(feed?.entry?.length || components.length);

  const theme = useTheme();

  const logTimestamp = useProfilerLogging();

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

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

  const renderRiverItem = React.useCallback(
    ({ item, index }) => {
      const readyToBeDisplayed = arePreviousComponentsLoaded(index);

      const styles = {
        display: !readyToBeDisplayed ? "none" : ("flex" as "none" | "flex"),
      };

      return (
        <View style={styles}>
          <ScreenLoadingMeasurementsListItemWrapper index={index}>
            <RiverItem
              {...{
                readyToBeDisplayed,
                riverId,
                item,
                index,
                isScreenWrappedInContainer,
                feed,
                groupId,
                onLoadFailed,
                onLoadFinished,
                getStaticComponentFeed,
                isLast: isLast(index, riverComponents.length),
              }}
            />
          </ScreenLoadingMeasurementsListItemWrapper>
        </View>
      );
    },
    [
      feed,
      getStaticComponentFeed,
      arePreviousComponentsLoaded,
      onLoadFailed,
      onLoadFinished,
    ]
  );

  const renderFooter = React.useCallback(() => {
    return (
      <RiverFooter
        visible={isAnyLoading}
        flatListHeight={flatListHeight}
        isAnyLoaded={isAnyLoaded || waitForAllComponents}
      />
    );
  }, [flatListHeight, isAnyLoading, isAnyLoaded]);

  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);

  React.useEffect(() => {
    if (isAllLoaded) {
      logTimestamp(riverId?.toString());
    }
  }, [isAllLoaded]);

  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) => {
    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);
      }
    }
  }, []);

  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}>
      <ScreenLoadingMeasurements
        riverId={riverId}
        numberOfComponents={feed?.entry?.length || components.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={Platform.OS === "android" ? "never" : "auto"}
            scrollIndicatorInsets={scrollIndicatorInsets}
            extraData={feed}
            onLayout={handleOnLayout}
            removeClippedSubviews
            initialNumToRender={3}
            maxToRenderPerBatch={10}
            windowSize={12}
            listKey={riverId}
            keyExtractor={keyExtractor}
            renderItem={renderRiverItem}
            data={riverComponents}
            contentContainerStyle={
              isScreenWrappedInContainer ? {} : screenStyle
            }
            ListFooterComponent={renderFooter}
            refreshControl={refreshControl}
            onScrollBeginDrag={onScrollBeginDrag}
            onScroll={onScroll}
            onMomentumScrollEnd={_onMomentumScrollEnd}
            onScrollEndDrag={_onScrollEndDrag}
            scrollEventThrottle={16}
            {...scrollViewExtraProps}
          />
        </ViewportTracker>
      </ScreenLoadingMeasurements>
    </View>
  );
}

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