import React, { FC, useContext, useRef } from 'react';
import {
  Animated,
  StyleSheet,
  TouchableOpacity,
  TouchableOpacityProps,
  View,
  ViewStyle,
  GestureResponderEvent,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { useComponentId } from '../Application';
import {
  ApplicationContext,
  ComponentContext,
  MiniAppContext,
  SkeletonContext,
} from '../Context';
import { Text } from '../Text';
import { Typography } from '../Text/types';
import { Colors, Spacing } from '../Consts';
import styles from './styles';
import { Icon } from '../Icon';
import { Skeleton } from '../Skeleton';
import LottieView from 'lottie-react-native';
import Reanimated, {
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

const AnimationLinear = Animated.createAnimatedComponent(LinearGradient);

export interface ButtonProps extends TouchableOpacityProps {
  /**
   * Defines the visual style of the button.
   */
  type?:
    | 'primary'
    | 'secondary'
    | 'tonal'
    | 'outline'
    | 'danger'
    | 'text'
    | 'disabled';

  /**
   *  Defines the size of the button.
   */
  size?: 'large' | 'medium' | 'small';
  full?: boolean;
  iconRight?: string;
  iconLeft?: string;
  title: string;
  tintColor?: string;
  loading?: boolean;
  onPress: () => void;
  params?: any;
}

const Button: FC<ButtonProps> = ({
  type = 'primary',
  size = 'large',
  tintColor = null,
  full = true,
  iconRight,
  iconLeft,
  loading = false,
  title = 'Button',
  params,
  accessibilityState,
  ...rest
}) => {
  const { theme, config } = useContext(ApplicationContext);
  const context = useContext<any>(MiniAppContext);
  const skeleton = useContext(SkeletonContext);
  const animationRef = useRef<LottieView>(null);
  const pressAnim = useSharedValue(0);

  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;

  const componentName = 'Button';
  const { componentId } = useComponentId(
    `${componentName}/${title}`,
    rest.accessibilityLabel,
  );

  /**
   * animate press in (Reanimated)
   */
  const animateIn = (event: GestureResponderEvent) => {
    rest?.onPressIn?.(event);
    pressAnim.value = withTiming(1, { duration: 100 });
  };

  /**
   * animate press out (Reanimated)
   */
  const animateOut = (event: GestureResponderEvent) => {
    rest?.onPressOut?.(event);
    pressAnim.value = withTiming(0, { duration: 100 });
  };

  /**
   * determine loading position
   * @param left
   * @param right
   * @param isLeft
   */
  const shouldShowLoading = (
    left?: string,
    right?: string,
    isLeft?: boolean,
  ): boolean => {
    const hasLeft = !!left;
    const hasRight = !!right;

    const loadingOnLeft = (!hasLeft && !hasRight) || (hasLeft && !hasRight);

    return isLeft === loadingOnLeft;
  };

  /**
   * export size style
   */
  const getSizeStyle = () => {
    const styleSheet: { [key: string]: any } = styles;
    return styleSheet[size ?? 'small'];
  };

  /**
   * export type style
   */
  const getTypeStyle = () => {
    switch (type) {
      case 'disabled':
        return {
          backgroundColor: theme.colors.background.disable,
        };
      case 'primary':
        return { backgroundColor: theme.colors.primary };
      case 'secondary':
        return {
          backgroundColor: theme.colors.background.surface,
          borderWidth: 1,
          borderColor: theme.colors.border.default,
        };
      case 'outline':
        return {
          backgroundColor: theme.colors.background.surface,
          borderWidth: 1,
          borderColor: color ?? theme.colors.primary,
        };
      case 'tonal':
        return {
          backgroundColor: theme.colors.background.tonal,
        };
      case 'danger':
        return {
          backgroundColor: theme.colors.error.primary,
        };
      case 'text':
        return {};
      default:
        return { backgroundColor: theme.colors.primary };
    }
  };

  /**
   * export icon size
   */
  const getIconSize = () => {
    switch (size) {
      case 'large':
        return 24;
      case 'medium':
      case 'small':
        return 16;
      default:
        return 24;
    }
  };

  /**
   * export icon space
   */
  const getIconSpace = () => {
    switch (size) {
      case 'large':
      case 'medium':
        return 8;
      case 'small':
        return 4;
      default:
        return 8;
    }
  };

  /**
   * export typography style
   */
  const getTypography = (): Typography => {
    switch (size) {
      case 'large':
        return 'action_default_bold';
      case 'medium':
        return 'action_s_bold';
      case 'small':
        return 'action_xs_bold';
      default:
        return 'action_default_bold';
    }
  };

  /**
   * export text color
   */
  const getTextColor = (): string => {
    switch (type) {
      case 'disabled':
        return theme.colors.text.disable;
      case 'primary':
        return Colors.black_01;
      case 'secondary':
        return theme.colors.text.default;
      case 'outline':
        return color ?? theme.colors.primary;
      case 'tonal':
        return theme.colors.primary;
      case 'danger':
        return Colors.black_01;
      case 'text':
        return color ?? theme.colors.primary;
      default:
        return Colors.black_01;
    }
  };

  /**
   * render title
   */
  const renderTitle = () => {
    const typography = getTypography();
    const textColor = getTextColor();
    return (
      <Text typography={typography} color={textColor} numberOfLines={1}>
        {title}
      </Text>
    );
  };

  /**
   * render icon
   * @param position
   */
  const renderIcon = (position: 'left' | 'right') => {
    const iconSize = getIconSize();
    const space = getIconSpace();
    const textColor = tintColor ?? getTextColor();

    const isLeft = position === 'left';
    const icon = isLeft ? iconLeft : iconRight;

    const style = [
      isLeft ? styles.leading : styles.trailing,
      {
        width: iconSize,
        height: iconSize,
        marginRight: isLeft ? space : 0,
        marginLeft: isLeft ? 0 : space,
      },
    ];

    const showLoading = shouldShowLoading(iconLeft, iconRight, isLeft);

    if (loading && showLoading) {
      return (
        <LottieView
          ref={animationRef}
          source={require('../Assets/lottie_circle_loader.json')}
          autoPlay
          loop
          colorFilters={[
            { keypath: 'loading 2.Ellipse 1.Stroke 1', color: textColor },
            { keypath: 'loading 3.Ellipse 1.Stroke 1', color: textColor },
            { keypath: 'base pink 2.Ellipse 1.Stroke 1', color: textColor },
          ]}
          style={[style, { borderRadius: Spacing.M }]}
        />
      );
    }

    if (icon) {
      return (
        <View style={style}>
          <Icon color={textColor} source={icon} size={iconSize} />
        </View>
      );
    }

    return null;
  };

  const { gradient, color } = config?.navigationBar?.buttonColors ?? {};
  const isDisabled = type === 'disabled' || loading;
  let gradientPros;
  if (gradient?.length > 0 && type === 'primary') {
    gradientPros = {
      colors: gradient,
      start: { x: 1, y: 0.5 },
      end: { x: 0, y: 0.5 },
    };
  }

  const sizeStyle = getSizeStyle();
  const typeStyle = getTypeStyle();
  const containerStyle: ViewStyle = StyleSheet.flatten([
    full && { width: '100%' },
    loading && { opacity: 0.75 },
  ]);
  const buttonStyle = StyleSheet.flatten([sizeStyle, typeStyle]);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      marginHorizontal: pressAnim.value * 2,
    };
  });

  if (skeleton?.loading) {
    return <Skeleton style={[buttonStyle, styles.skeleton]} />;
  }

  return (
    <ComponentContext.Provider
      value={{
        componentName,
        componentId,
        params,
        action: 'click',
      }}
    >
      <TouchableOpacity
        {...rest}
        accessibilityLabel={componentId}
        accessibilityRole="button"
        accessibilityState={{
          disabled: isDisabled,
          busy: loading,
          ...accessibilityState,
        }}
        disabled={isDisabled}
        activeOpacity={0.75}
        style={[containerStyle, showBaseLineDebug && styles.debugBaseLine]}
        onPressIn={animateIn}
        onPressOut={animateOut}
      >
        <Reanimated.View style={[buttonStyle, animatedStyle]}>
        {renderIcon('left')}
          {renderTitle()}
          {renderIcon('right')}
          {gradientPros && (
            <AnimationLinear {...gradientPros} style={styles.gradientView} />
          )}
          {gradientPros && <View style={styles.strokeView} />}
        </Reanimated.View>
      </TouchableOpacity>
    </ComponentContext.Provider>
  );
};

export { Button };
