import { StackNavigationOptions } from '@react-navigation/stack';
import { useHeaderHeight } from '@react-navigation/elements';
import React, {
  forwardRef,
  Fragment,
  ReactNode,
  Ref,
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useRef,
} from 'react';
import {
  Animated,
  KeyboardAvoidingView,
  NativeScrollEvent,
  NativeSyntheticEvent,
  Platform,
  ScrollView,
  ScrollViewProps,
  StatusBar,
  StatusBarStyle,
  useWindowDimensions,
  View,
  ViewProps,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ApplicationContext, ScreenContext } from '../Context';
import Navigation from '../Application/Navigation';
import {
  AnimatedHeader,
  NavigationOptions,
  ScreenTrackingParams,
  SearchHeaderProps,
} from '../Application/types';
import { Colors, Spacing, Styles } from '../Consts';
import { FloatingButton, FloatingButtonProps } from './FloatingButton';
import styles from './styles';
import { HeaderType } from './types';
import { InputRef } from '../Input';
import { exportHeaderTitle, getOptions } from '../Application/utils';
import { validateChildren } from './utils';
import Section from './Section';
import {
  HeaderBackground,
  HeaderExtendHeader,
  HeaderLeft,
  HeaderTitle,
  SearchHeader,
} from '../Application';

export interface ScreenProps extends ViewProps {
  /**
   * ReactNode representing the content of the screen.
   */
  children: React.ReactNode;
  /**
   * Optional. Navigation object for handling navigation actions.
   */
  navigation?: Navigation;

  /**
   * Optional. If `true`, enable keyboard avoiding view for the screen.
   */
  enableKeyboardAvoidingView?: boolean;

  /**
   * Optional. If `true`, the screen content is scrollable.
   */
  scrollable?: boolean;

  /**
   * Optional. Set type of header.
   */
  headerType?: HeaderType;

  /**
   * Optional. Props for the animated component or bool used top navigation animated.
   */
  animatedHeader?: AnimatedHeader;

  /**
   * Optional. Props for the underlying ScrollView component when `scrollable` is `true`.
   */
  scrollViewProps?: ScrollViewProps;

  /**
   * Optional. ReactNode representing the header component of the screen.
   */
  headerComponent?: ReactNode;

  /**
   * Optional. ReactNode representing the footer component of the screen.
   */
  footerComponent?: ReactNode;

  /**
   * Optional. Background color of the screen.
   */
  backgroundColor?: string;

  /**
   * Optional. layout card offset overlap header banner.
   */
  layoutOffset?: -8 | -24 | -56;

  /**
   * Optional. Width of the right header.
   */
  headerRightWidth?: 0 | 73 | 110 | number;

  /**
   * Optional. Props for the floating action button.
   */
  floatingButtonProps?: Omit<FloatingButtonProps, 'animatedValue' | 'bottom'>;

  /**
   * Optional. Keyboard vertical offset.
   */
  keyboardVerticalOffset?: number;

  /**
   * Optional. If `true`, the screen content is using grid layout by span.
   */
  useGridLayout?: boolean;

  /**
   * Optional. specs for input search.
   */
  inputSearchProps?: SearchHeaderProps;

  /**
   * Optional. Ref for input search.
   */
  inputSearchRef?: Ref<InputRef>;

  /**
   * Optional. Animated value for header.
   */
  animatedValue?: Animated.Value;

  /**
   * Optional. If `true`, use shadow header.
   */
  useShadowHeader?: boolean;

  /**
   * Optional. Custom header Gradient Color.
   */
  gradientColor?: string;

  /**
   * Optional. Custom headerBackground Image
   */
  headerBackground?: string;

  /**
   * Optional. Custom tracking params
   */
  trackingParams?: ScreenTrackingParams;
}

const Screen = forwardRef(
  (
    {
      children,
      enableKeyboardAvoidingView = false,
      scrollable = false,
      navigation,
      animatedHeader,
      scrollViewProps,
      headerComponent: Header,
      footerComponent: Footer,
      layoutOffset = -56,
      headerRightWidth = 73,
      backgroundColor,
      headerType = 'default',
      floatingButtonProps,
      useGridLayout = true,
      useShadowHeader = true,
      keyboardVerticalOffset,
      inputSearchProps,
      inputSearchRef,
      animatedValue: customAnimatedValue,
      headerBackground,
      gradientColor,
      trackingParams,
    }: ScreenProps,
    ref: any,
  ) => {
    const screenRef = useRef<View | ScrollView>(null);
    const { width: widthDevice } = useWindowDimensions();
    const { theme } = useContext(ApplicationContext);
    const screen: any = useContext(ScreenContext);
    const insets = useSafeAreaInsets();
    const heightHeader = useHeaderHeight();
    const animatedValue = useRef<Animated.Value>(
      customAnimatedValue || new Animated.Value(0),
    );
    const currentTint = useRef<string | undefined>(undefined);
    const isTab = navigation?.instance?.getState?.()?.type === 'tab';

    let handleScroll;
    let Component: any = View;

    let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
    if (headerType === 'extended' || animatedHeader || inputSearchProps) {
      keyboardOffset = -Math.min(insets.bottom, 21);
    }

    /**
     * inject params for screen tracking
     */
    screen?.onSetParams?.(trackingParams);

    /**
     * export options for screen
     * @param headerType
     */
    const setHeaderType = useCallback(
      (headerType: HeaderType) => {
        let options: StackNavigationOptions;
        switch (headerType) {
          case 'none':
            options = {
              headerTransparent: true,
              headerBackground: () => null,
              headerShown: false,
            };
            break;

          case 'extended':
            options = {
              headerShown: true,
              headerTransparent: true,
              headerBackground: () => null,
              headerTitle: (props: any) => <HeaderTitle {...props} />,
            };
            if (inputSearchProps) {
              options = {
                headerShown: true,
                headerTransparent: true,
                headerBackground: () => null,
                headerTitle: (props: any) => (
                  <HeaderTitle
                    {...props}
                    interpolate={{
                      inputRange: [0, 50],
                      outputRange: [1, 0],
                      extrapolate: 'clamp',
                    }}
                    animatedValue={animatedValue.current}
                  />
                ),
              };
            }
            break;

          default:
            options = {
              headerTransparent: false,
              headerTintColor: Colors.black_17,
              headerShown: true,
              headerBackground: (props: any) => (
                <HeaderBackground
                  {...props}
                  animatedValue={animatedValue.current}
                  useShadowHeader={useShadowHeader}
                  headerBackground={headerBackground}
                  gradientColor={gradientColor}
                />
              ),
              headerTitle: (props: any) => <HeaderTitle {...props} />,
            };
            if (inputSearchProps) {
              options = {
                headerShown: true,
                headerTransparent: true,
                headerBackground: () => null,
                headerTitle: (props: any) => (
                  <HeaderTitle
                    {...props}
                    interpolate={{
                      inputRange: [0, 50],
                      outputRange: [1, 0],
                      extrapolate: 'clamp',
                    }}
                    animatedValue={animatedValue.current}
                  />
                ),
              };
            }
        }

        navigation?.instance?.setOptions(options);
      },
      [
        gradientColor,
        headerBackground,
        inputSearchProps,
        navigation?.instance,
        useShadowHeader,
      ],
    );

    /**
     * export animatedHeader options for screen
     * @param animatedHeader
     */
    const setAnimatedHeader = useCallback(
      (animatedHeader: AnimatedHeader) => {
        let options: StackNavigationOptions;
        if (!currentTint.current) {
          currentTint.current = animatedHeader?.headerTintColor;
        }

        options = {
          headerTintColor: currentTint.current ?? Colors.black_17,
          headerTransparent: true,
          headerBackground: (props: any) => (
            <HeaderBackground
              {...props}
              animatedValue={animatedValue.current}
              useGradient={false}
              useShadowHeader={useShadowHeader}
              headerBackground={headerBackground}
              gradientColor={gradientColor}
            />
          ),
          headerTitle: (props: any) => {
            return <HeaderTitle {...props} />;
          },
        };

        if (animatedHeader.headerTitle) {
          options = {
            ...options,
            ...exportHeaderTitle(animatedHeader),
          };
        }

        navigation?.instance?.setOptions(options);
      },
      [gradientColor, headerBackground, navigation?.instance, useShadowHeader],
    );

    /**
     * export animatedHeader options for screen
     */
    const setAnimatedSearch = useCallback(() => {
      const options: StackNavigationOptions = {
        headerShown: true,
        headerTransparent: true,
        headerBackground: () => null,
        headerTitle: (props: any) => (
          <HeaderTitle
            {...props}
            interpolate={{
              inputRange: [0, 50],
              outputRange: [1, 0],
              extrapolate: 'clamp',
            }}
            animatedValue={animatedValue.current}
          />
        ),
      };

      navigation?.instance?.setOptions(options);
    }, [navigation?.instance]);

    /**
     * export search header
     */
    const setSearchHeader = (params: SearchHeaderProps) => {
      const searchWidth = widthDevice - 24;
      const options: StackNavigationOptions = {
        headerRight: undefined,
        headerTitleContainerStyle: {
          width: searchWidth,
          maxWidth: searchWidth,
          marginLeft: 0,
          marginEnd: 0,
          marginStart: 0,
          marginHorizontal: 0,
          marginVertical: 0,
          margin: 0,
          left: Spacing.M,
        },
        headerLeft: (props: any) =>
          params?.hiddenBack ? null : <HeaderLeft {...props} />,
        headerTitle: () => (
          <SearchHeader {...params} animatedValue={animatedValue.current} />
        ),
      };

      navigation?.instance?.setOptions(options);
    };

    useEffect(() => {
      const focus = navigation?.instance?.addListener?.('focus', () => {
        let barStyle: StatusBarStyle = 'dark-content';
        if (currentTint.current === Colors.black_01) {
          barStyle = 'light-content';
        }
        StatusBar.setBarStyle(barStyle, true);
      });

      if (animatedHeader) {
        setAnimatedHeader(animatedHeader);
      } else if (inputSearchProps) {
        setAnimatedSearch();
      } else {
        setHeaderType(headerType);
      }

      return () => {
        focus?.();
      };
    }, [
      headerType,
      animatedHeader,
      inputSearchProps,
      useShadowHeader,
      navigation?.instance,
      setAnimatedHeader,
      setAnimatedSearch,
      setHeaderType,
    ]);

    /**
     * expose ref for screen
     */
    useImperativeHandle(ref, () => {
      return Object.assign(screenRef.current || {}, {
        setOptions: (params: NavigationOptions) => {
          const options = getOptions(params, undefined);
          navigation?.instance?.setOptions(options);
        },
        setSearchHeader: setSearchHeader,
      });
    });

    /**
     * animated when use scroll && animated value
     */
    if (scrollable) {
      Component = Animated.ScrollView;
      handleScroll = Animated.event(
        [
          {
            nativeEvent: {
              contentOffset: { y: animatedValue.current as Animated.Value },
            },
          },
        ],
        {
          useNativeDriver: true,
          listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
            scrollViewProps?.onScroll?.(e);
            if (animatedHeader) {
              const offsetY = e.nativeEvent.contentOffset.y;
              let color = animatedHeader?.headerTintColor ?? Colors.black_17;
              if (offsetY > 50) {
                color = Colors.black_17;
              }
              if (color !== currentTint.current) {
                currentTint.current = color;
                navigation?.setOptions({
                  headerTintColor: color,
                });

                let barStyle: StatusBarStyle = 'dark-content';
                if (currentTint.current === Colors.black_01) {
                  barStyle = 'light-content';
                }
                StatusBar.setBarStyle(barStyle, true);
              }
            }
          },
        },
      );
    }

    /**
     * handle scroll end
     * @param e
     */
    const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
      const offsetY = e.nativeEvent.contentOffset.y;
      if (inputSearchProps && offsetY < 100 && offsetY > 0) {
        Animated.timing(animatedValue.current, {
          toValue: 0,
          useNativeDriver: true,
          duration: 300,
        }).start();
        ref?.scrollTo?.({ y: 0, animated: true });
      }
      scrollViewProps?.onScrollEndDrag?.(e);
    };

    /**
     * render top navigation banner
     */
    const renderAnimatedHeader = () => {
      if (typeof animatedHeader?.component === 'function') {
        return (
          <View
            style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
          >
            {animatedHeader?.component({
              animatedValue: animatedValue.current,
            })}
          </View>
        );
      }
    };

    /**
     * build content for screen for grid rule
     */
    const renderContent = (children: any): any => {
      const results = validateChildren(children, [undefined]);
      if (Array.isArray(results)) {
        return results.map((item, index) => {
          const space = item?.props?.useMargin === false ? 0 : Spacing.M;
          if (item?.type === Fragment) {
            return renderContent(item?.props?.children);
          }
          if (item) {
            return (
              <View
                key={`ScreenItem${index}`}
                style={{
                  marginTop: space,
                  flex: item.props?.expanded ? 1 : undefined,
                }}
                onLayout={item.props?.onLayout}
              >
                {item}
              </View>
            );
          }
        });
      } else {
        const item: any = children;
        const space = item?.props?.useMargin === false ? 0 : Spacing.M;
        if (item?.type === Fragment) {
          return renderContent(item?.props?.children);
        }
        return (
          <View
            key={`ScreenItem`}
            style={{
              marginTop: space,
              flex: item?.props?.expanded ? 1 : undefined,
            }}
            onLayout={item?.props?.onLayout}
          >
            {children}
          </View>
        );
      }
    };

    return (
      <View
        style={[
          Styles.flex,
          {
            backgroundColor: backgroundColor ?? theme.colors.background.default,
          },
        ]}
      >
        <HeaderExtendHeader
          headerType={headerType}
          heightHeader={heightHeader}
          headerRightWidth={headerRightWidth}
          animatedValue={animatedValue.current}
          inputSearchProps={inputSearchProps}
          navigation={navigation}
          inputSearchRef={inputSearchRef}
          useShadowHeader={useShadowHeader}
          gradientColor={gradientColor}
          headerBackground={headerBackground}
        />

        <KeyboardAvoidingView
          style={Styles.flex}
          enabled={enableKeyboardAvoidingView}
          keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
          behavior={Platform.select({
            ios: 'padding',
            android: undefined,
          })}
        >
          {Header}

          <Component
            {...scrollViewProps}
            ref={screenRef}
            showsVerticalScrollIndicator={false}
            onScroll={handleScroll}
            onScrollEndDrag={handleScrollEnd}
            scrollEventThrottle={16}
            style={Styles.flex}
          >
            {renderAnimatedHeader()}

            {useGridLayout ? renderContent(children) : children}
          </Component>

          {floatingButtonProps && (
            <View>
              <FloatingButton
                {...floatingButtonProps}
                animatedValue={animatedValue.current}
                bottom={
                  Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
                }
              />
            </View>
          )}

          {Footer && (
            <View
              style={[
                styles.shadow,
                {
                  paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
                  backgroundColor: theme.colors.background.surface,
                },
              ]}
            >
              <Section>{Footer}</Section>
            </View>
          )}
        </KeyboardAvoidingView>
      </View>
    );
  },
);

export default Screen;
