import type { ReactElement } from 'react';
import React, { isValidElement, useCallback, useEffect, useState } from 'react';
import type {
  ImageSourcePropType,
  ViewProps,
  ImageProps as RNImageProps,
} from 'react-native';
import type { ErrorVariant } from './StyledError';
import {
  StyledErrorButtonContainer,
  StyledErrorButtonSecondary,
  StyledErrorContainer,
  StyledErrorContent,
  StyledErrorDescription,
  StyledErrorIconContainer,
  StyledErrorImage,
  StyledErrorImageContainer,
  StyledErrorModal,
  StyledErrorTitle,
} from './StyledError';
import { useDeprecation } from '../../../utils/hooks';
import type { ImageProps } from '../../Image/index';
import Illustration from '../../Illustration';
import type { IllustrationName } from '../../Illustration';
import Button from '../../Button';

interface ErrorProps extends ViewProps {
  /**
   * @deprecated use conditional rendering instead
   *
   * Visibility of the error
   */
  visible?: boolean;
  /**
   * Variant of the error
   */
  variant?: ErrorVariant;
  /**
   * Title of the error
   */
  title: string | ReactElement;
  /**
   * Description of the error
   */
  description?: string;
  /**
   * Image of the error
   * @deprecated The `image` prop is deprecated and will be removed in the next major version. Use the `icon` prop instead.
   */
  image?:
    | ReactElement<ImageProps | RNImageProps>
    | ImageSourcePropType
    | string;
  /**
   * Testing id of the component.
   */
  testID?: string;
  /**
   * Action button text
   */
  ctaText?: string;
  /**
   * Callback when the action button is pressed.
   */
  onCtaPress?: () => void;
  /**
   * Secondary button text
   */
  secondaryCtaText?: string;
  /**
   * Callback when the secondary button is pressed.
   */
  onSecondaryCtaPress?: () => void;
  /**
   * Status icon to be displayed, this will replace the deprecated image prop.
   */
  icon?: IllustrationName;
}

type onCloseCallbackType = 'cta' | 'secondaryCta' | null;

const renderImage = (
  image: ReactElement<ImageProps | RNImageProps> | ImageSourcePropType | string
) => {
  if (isValidElement(image)) {
    return React.cloneElement(image, {
      testID: 'error-image',
    });
  }

  return (
    <StyledErrorImage
      source={typeof image === 'string' ? { uri: image } : image}
      testID="error-image"
    />
  );
};

const renderImageOrIcon = ({
  image,
  icon,
}: {
  image?:
    | ReactElement<ImageProps | RNImageProps>
    | ImageSourcePropType
    | string;
  icon?: IllustrationName;
}) => {
  if (icon) {
    return (
      <StyledErrorIconContainer>
        <Illustration name={icon} testID="error-icon" />
      </StyledErrorIconContainer>
    );
  }

  if (image) {
    return (
      <StyledErrorImageContainer>
        {renderImage(image)}
      </StyledErrorImageContainer>
    );
  }

  return null;
};

const ErrorPage = ({
  variant = 'in-page',
  title,
  description,
  image,
  testID,
  ctaText,
  onCtaPress,
  secondaryCtaText,
  onSecondaryCtaPress,
  icon,
  ...nativeProps
}: ErrorProps): ReactElement => {
  const showCta = ctaText && onCtaPress !== undefined;
  const showSecondaryCta =
    secondaryCtaText && onSecondaryCtaPress !== undefined;
  const showButtonContainer = showCta || showSecondaryCta;
  return (
    <StyledErrorContainer
      testID={testID}
      themeVariant={variant}
      {...nativeProps}
    >
      <StyledErrorContent>
        {renderImageOrIcon({ image, icon })}
        <StyledErrorTitle level="h4" typeface="playful">
          {title}
        </StyledErrorTitle>

        {description && (
          <StyledErrorDescription typeface="playful">
            {description}
          </StyledErrorDescription>
        )}
      </StyledErrorContent>

      {showButtonContainer && (
        <StyledErrorButtonContainer>
          {showCta && (
            <Button variant="filled" text={ctaText} onPress={onCtaPress} />
          )}
          {showSecondaryCta && (
            <StyledErrorButtonSecondary
              variant="text"
              text={secondaryCtaText}
              onPress={onSecondaryCtaPress}
            />
          )}
        </StyledErrorButtonContainer>
      )}
    </StyledErrorContainer>
  );
};
/**
 * Renders error page
 *
 * @param {ErrorProps}
 * @return {*}  {ReactElement}
 */
const ErrorComponent = ({
  visible: _visible,
  variant = 'in-page',
  title,
  description,
  image,
  testID,
  ctaText,
  onCtaPress,
  secondaryCtaText,
  onSecondaryCtaPress,
  ...nativeProps
}: ErrorProps): ReactElement => {
  useDeprecation(
    `Visible prop is deprecated. Use conditional rendering instead`,
    _visible !== undefined
  );
  const visible = _visible === undefined ? true : _visible;

  const [isVisible, setIsVisible] = useState(visible);
  const [ctaPressed, setCtaPressed] = useState<onCloseCallbackType>(null);

  const onCloseModal = useCallback(() => {
    if (ctaPressed === 'cta' && onCtaPress) {
      onCtaPress();
    }
    if (ctaPressed === 'secondaryCta' && onSecondaryCtaPress) {
      onSecondaryCtaPress();
    }
    setCtaPressed(null);
  }, [ctaPressed, onCtaPress, onSecondaryCtaPress]);

  useEffect(() => {
    setIsVisible(visible);
  }, [visible]);

  // These useEffect lines prevents race condition error when callback contains navigation logic
  useEffect(() => {
    if (ctaPressed) {
      setIsVisible(false);
    }
  }, [ctaPressed]);

  useEffect(() => {
    if (!isVisible) {
      onCloseModal();
    }
  }, [isVisible, onCloseModal]);

  if (variant === 'full-screen') {
    return (
      <StyledErrorModal
        visible={isVisible}
        onRequestClose={() => setIsVisible(false)}
        onDismiss={() => setIsVisible(false)}
        animationType="slide"
      >
        <ErrorPage
          variant={variant}
          title={title}
          description={description}
          image={image}
          testID={testID}
          ctaText={ctaText}
          onCtaPress={
            onCtaPress &&
            (() => {
              setCtaPressed('cta');
            })
          }
          secondaryCtaText={secondaryCtaText}
          onSecondaryCtaPress={
            onSecondaryCtaPress &&
            (() => {
              setCtaPressed('secondaryCta');
            })
          }
          {...nativeProps}
        />
      </StyledErrorModal>
    );
  }

  return (
    <ErrorPage
      variant={variant}
      title={title}
      description={description}
      image={image}
      testID={testID}
      ctaText={ctaText}
      onCtaPress={onCtaPress}
      secondaryCtaText={secondaryCtaText}
      onSecondaryCtaPress={onSecondaryCtaPress}
      {...nativeProps}
    />
  );
};

export default ErrorComponent;
