import React, { useMemo } from 'react';
import { ColorValue, Platform, Pressable, StyleSheet, View, ViewStyle } from 'react-native';

import { MessageTextContainer } from './MessageTextContainer';

import { useA11yLabel } from '../../../a11y/hooks/useA11yLabel';
import { useChatContext } from '../../../contexts';
import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext';
import {
  MessageContextValue,
  useMessageContext,
} from '../../../contexts/messageContext/MessageContext';
import { useMessageListItemContext } from '../../../contexts/messageListItemContext/MessageListItemContext';
import {
  MessagesContextValue,
  useMessagesContext,
} from '../../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
import {
  TranslationContextValue,
  useTranslationContext,
} from '../../../contexts/translationContext/TranslationContext';

import { components, primitives } from '../../../theme';
import { FileTypes } from '../../../types/types';
import { checkMessageEquality, checkQuotedMessageEquality } from '../../../utils/utils';
import { Poll } from '../../Poll/Poll';

const useReplyStyles = () => {
  const {
    theme: { semantics },
  } = useTheme();
  const { isMyMessage } = useMessageContext();

  return useMemo(() => {
    return StyleSheet.create({
      container: {
        minWidth: 256, // TODO: Not sure how to fix this
        backgroundColor: isMyMessage
          ? semantics.chatBgAttachmentOutgoing
          : semantics.chatBgAttachmentIncoming,
        paddingLeft: primitives.spacingSm,
      },
      leftContainer: {
        borderLeftColor: isMyMessage
          ? semantics.chatReplyIndicatorOutgoing
          : semantics.chatReplyIndicatorIncoming,
      },
    });
  }, [semantics, isMyMessage]);
};

export type MessageContentPropsWithContext = Pick<
  MessageContextValue,
  | 'alignment'
  | 'goToMessage'
  | 'groupStyles'
  | 'hasInteractiveAccessibilityContent'
  | 'isMyMessage'
  | 'message'
  | 'messageContentOrder'
  | 'onLongPress'
  | 'onPress'
  | 'onPressIn'
  | 'otherAttachments'
  | 'preventPress'
  | 'threadList'
  | 'isMessageAIGenerated'
> &
  Pick<
    MessagesContextValue,
    | 'additionalPressableProps'
    | 'enableMessageGroupingByUser'
    | 'isAttachmentEqual'
    | 'myMessageTheme'
  > &
  Pick<TranslationContextValue, 't'> & {
    /**
     * Background color for the message content
     */
    backgroundColor?: ColorValue;
    /**
     * If the message is the very last message in the message list
     */
    isVeryLastMessage?: boolean;
    /**
     * If the message has no border radius
     */
    noBorder?: boolean;
    /**
     * If the message is grouped in a single or bottom container
     */
    messageGroupedSingleOrBottom?: boolean;

    /**
     * If the message has a single file
     */
    isSingleFile?: boolean;
    hidePaddingTop?: boolean;
    hidePaddingHorizontal?: boolean;
    hidePaddingBottom?: boolean;
  };

/**
 * Child of MessageItemView that displays a message's content
 */
const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
  const {
    additionalPressableProps,
    alignment,
    backgroundColor,
    enableMessageGroupingByUser,
    groupStyles,
    goToMessage,
    hasInteractiveAccessibilityContent,
    isMessageAIGenerated,
    isMyMessage,
    isVeryLastMessage,
    message,
    messageContentOrder,
    messageGroupedSingleOrBottom = false,
    noBorder,
    onLongPress,
    onPress,
    onPressIn,
    otherAttachments,
    preventPress,
    hidePaddingTop,
    hidePaddingHorizontal,
    hidePaddingBottom,
  } = props;
  const { client } = useChatContext();
  const accessibilityHint = useA11yLabel('a11y/Double tap and hold to activate contextual menu');
  const a11ySenderLabel = useA11yLabel(
    isMyMessage ? 'a11y/Message from you' : 'a11y/Message from {{sender}}',
    isMyMessage ? undefined : { sender: message.user?.name || message.user?.id || '' },
  );
  const {
    Attachment,
    FileAttachmentGroup,
    Gallery,
    MessageContentBottomView,
    MessageContentLeadingView,
    MessageContentTopView,
    MessageContentTrailingView,
    MessageLocation,
    Reply,
    StreamingMessageView,
  } = useComponentsContext();
  const replyStyles = useReplyStyles();

  const {
    theme: {
      messageItemView: {
        content: {
          container: {
            borderBottomLeftRadius,
            borderBottomRightRadius,
            borderRadius,
            borderTopLeftRadius,
            borderTopRightRadius,
            ...container
          },
          containerInner,
          contentContainer,
          lastMessageContainer,
          messageGroupedSingleOrBottomContainer,
          messageGroupedTopContainer,
          replyContainer,
        },
      },
    },
  } = useTheme();

  const isAIGenerated = useMemo(
    () => isMessageAIGenerated(message),
    [message, isMessageAIGenerated],
  );

  // Merged background-color + border-radius object passed directly into the
  // bubble's style array (no spread at the call site). Theme-defined radii
  // override the group-position-computed defaults; theme-undefined radii are
  // omitted so they don't override the computed defaults.
  const bubbleColorAndRadius = useMemo<ViewStyle>(() => {
    // enum('top', 'middle', 'bottom', 'single')
    const groupPosition = groupStyles?.[0];
    const isBottomOrSingle = groupPosition === 'single' || groupPosition === 'bottom';

    let computedBottomLeftRadius = components.messageBubbleRadiusGroupBottom;
    let computedBottomRightRadius = components.messageBubbleRadiusGroupBottom;
    if (isBottomOrSingle) {
      // add relevant sharp corner (the "tail")
      if (alignment === 'right') {
        computedBottomRightRadius = components.messageBubbleRadiusTail;
      } else {
        computedBottomLeftRadius = components.messageBubbleRadiusTail;
      }
    }

    const style: ViewStyle = {
      backgroundColor,
      borderBottomLeftRadius: borderBottomLeftRadius ?? computedBottomLeftRadius,
      borderBottomRightRadius: borderBottomRightRadius ?? computedBottomRightRadius,
    };
    if (borderRadius !== undefined) style.borderRadius = borderRadius;
    if (borderTopLeftRadius !== undefined) style.borderTopLeftRadius = borderTopLeftRadius;
    if (borderTopRightRadius !== undefined) style.borderTopRightRadius = borderTopRightRadius;

    return style;
  }, [
    alignment,
    backgroundColor,
    borderBottomLeftRadius,
    borderBottomRightRadius,
    borderRadius,
    borderTopLeftRadius,
    borderTopRightRadius,
    groupStyles,
  ]);

  const { setNativeScrollability } = useMessageListItemContext();
  const hasContentSideViews = !!(MessageContentLeadingView || MessageContentTrailingView);
  const gap = primitives.spacingXs;

  const messageTextContainerStyles = useMemo(() => {
    return {
      textContainer: {
        // Cancel the container's 8px inter-item gap so only the caption's own
        // paragraph marginTop shows. Skip for the first item: there is no gap to
        // cancel
        marginTop: -gap,
      },
    };
  }, [gap]);

  const contentBody = (
    <>
      <View
        style={[
          {
            gap,
            paddingTop: hidePaddingTop ? 0 : primitives.spacingXs,
            paddingHorizontal: hidePaddingHorizontal ? 0 : primitives.spacingXs,
            paddingBottom: hidePaddingBottom ? 0 : primitives.spacingXs,
          },
          contentContainer,
        ]}
      >
        {messageContentOrder.map((messageContentType, messageContentOrderIndex) => {
          switch (messageContentType) {
            case 'quoted_reply':
              return (
                message.quoted_message && (
                  <Pressable
                    disabled={!goToMessage || preventPress}
                    key={`quoted_reply_${messageContentOrderIndex}`}
                    onLongPress={(event) => {
                      if (onLongPress) {
                        onLongPress({
                          emitter: 'messageContent',
                          event,
                        });
                      }
                    }}
                    onPress={(event) => {
                      if (!message.quoted_message || !goToMessage) {
                        return;
                      }

                      if (onPress) {
                        onPress({
                          defaultHandler: () => goToMessage(message.quoted_message!.id),
                          emitter: 'messageContent',
                          event,
                        });
                        return;
                      }

                      goToMessage(message.quoted_message.id);
                    }}
                    onPressIn={(event) => {
                      if (onPressIn) {
                        onPressIn({
                          emitter: 'messageContent',
                          event,
                        });
                      }
                    }}
                    style={[styles.replyContainer, replyContainer]}
                  >
                    <Reply mode='reply' styles={replyStyles} />
                  </Pressable>
                )
              );
            case 'attachments':
              return otherAttachments.map((attachment, attachmentIndex) => (
                <Attachment attachment={attachment} key={`attachment-${attachmentIndex}`} />
              ));
            case 'files':
              return (
                <FileAttachmentGroup key={`file_attachment_group_${messageContentOrderIndex}`} />
              );
            case 'gallery':
              return (
                <View key={`gallery_${messageContentOrderIndex}`} style={styles.galleryContainer}>
                  <Gallery />
                </View>
              );
            case 'poll': {
              const pollId = message.poll_id;
              const poll = pollId && client.polls.fromState(pollId);
              return pollId && poll ? (
                <Poll key={`poll_${messageContentOrderIndex}`} message={message} poll={poll} />
              ) : null;
            }
            case 'location':
              return MessageLocation ? (
                <MessageLocation
                  key={`message_location_${messageContentOrderIndex}`}
                  message={message}
                />
              ) : null;
            case 'ai_text':
              return isAIGenerated ? (
                <StreamingMessageView
                  key={`ai_message_text_container_${messageContentOrderIndex}`}
                />
              ) : null;
            case 'text': {
              const suppressed =
                (otherAttachments.length && otherAttachments[0].actions) || isAIGenerated;
              return suppressed ? null : (
                <MessageTextContainer
                  key={`message_text_container_${messageContentOrderIndex}`}
                  styles={messageContentOrderIndex === 0 ? undefined : messageTextContainerStyles}
                />
              );
            }
            default:
              return null;
          }
        })}
      </View>
    </>
  );
  const a11yPressableLabel = useMemo(() => {
    if (!a11ySenderLabel) return undefined;
    return message.text && !hasInteractiveAccessibilityContent
      ? `${a11ySenderLabel}. ${message.text}`
      : a11ySenderLabel;
  }, [a11ySenderLabel, hasInteractiveAccessibilityContent, message.text]);

  return (
    <Pressable
      accessibilityLabel={a11yPressableLabel}
      accessibilityHint={accessibilityHint}
      accessible={hasInteractiveAccessibilityContent ? false : undefined}
      disabled={preventPress}
      onLongPress={(event) => {
        if (onLongPress) {
          onLongPress({
            emitter: 'messageContent',
            event,
          });
        }
      }}
      onPress={(event) => {
        if (onPress) {
          onPress({
            emitter: 'messageContent',
            event,
          });
        }
      }}
      onPressIn={(event) => {
        if (onPressIn) {
          onPressIn({
            emitter: 'messageContent',
            event,
          });
        }
      }}
      style={container}
      {...additionalPressableProps}
      onPressOut={(event) => {
        setNativeScrollability(true);

        if (additionalPressableProps?.onPressOut) {
          additionalPressableProps.onPressOut(event);
        }
      }}
    >
      <View
        style={[
          styles.containerInner,
          bubbleColorAndRadius,
          noBorder ? styles.noBorder : null,
          containerInner,
          messageGroupedSingleOrBottom
            ? isVeryLastMessage && enableMessageGroupingByUser
              ? lastMessageContainer
              : messageGroupedSingleOrBottomContainer
            : messageGroupedTopContainer,
        ]}
        testID='message-content-wrapper'
      >
        {a11ySenderLabel && Platform.OS !== 'android' && hasInteractiveAccessibilityContent ? (
          <View
            accessibilityLabel={a11ySenderLabel}
            accessibilityHint={accessibilityHint}
            accessible
            pointerEvents='none'
            style={StyleSheet.absoluteFill}
          />
        ) : null}
        {MessageContentTopView ? <MessageContentTopView /> : null}
        {hasContentSideViews ? (
          <View
            style={[
              styles.contentRow,
              alignment === 'right' ? styles.rightAlignContentRow : undefined,
            ]}
            testID='message-content-row'
          >
            {MessageContentLeadingView ? <MessageContentLeadingView /> : null}
            <View style={styles.contentBody}>{contentBody}</View>
            {MessageContentTrailingView ? <MessageContentTrailingView /> : null}
          </View>
        ) : (
          contentBody
        )}
        {MessageContentBottomView ? <MessageContentBottomView /> : null}
      </View>
    </Pressable>
  );
};

const areEqual = (
  prevProps: MessageContentPropsWithContext,
  nextProps: MessageContentPropsWithContext,
) => {
  const {
    alignment: prevAlignment,
    backgroundColor: prevBackgroundColor,
    preventPress: prevPreventPress,
    goToMessage: prevGoToMessage,
    groupStyles: prevGroupStyles,
    hasInteractiveAccessibilityContent: prevHasInteractiveAccessibilityContent,
    isAttachmentEqual,
    message: prevMessage,
    messageContentOrder: prevMessageContentOrder,
    myMessageTheme: prevMyMessageTheme,
    otherAttachments: prevOtherAttachments,
    t: prevT,
  } = prevProps;
  const {
    alignment: nextAlignment,
    backgroundColor: nextBackgroundColor,
    preventPress: nextPreventPress,
    goToMessage: nextGoToMessage,
    groupStyles: nextGroupStyles,
    hasInteractiveAccessibilityContent: nextHasInteractiveAccessibilityContent,
    message: nextMessage,
    messageContentOrder: nextMessageContentOrder,
    myMessageTheme: nextMyMessageTheme,
    otherAttachments: nextOtherAttachments,
    t: nextT,
  } = nextProps;

  if (prevHasInteractiveAccessibilityContent !== nextHasInteractiveAccessibilityContent) {
    return false;
  }

  if (prevBackgroundColor !== nextBackgroundColor) {
    return false;
  }

  if (prevAlignment !== nextAlignment) {
    return false;
  }

  if (prevPreventPress !== nextPreventPress) {
    return false;
  }

  const goToMessageChangedAndMatters =
    nextMessage.quoted_message_id && prevGoToMessage !== nextGoToMessage;
  if (goToMessageChangedAndMatters) {
    return false;
  }

  const otherAttachmentsEqual =
    prevOtherAttachments.length === nextOtherAttachments.length &&
    prevOtherAttachments?.[0]?.actions?.length === nextOtherAttachments?.[0]?.actions?.length;
  if (!otherAttachmentsEqual) {
    return false;
  }

  const groupStylesEqual = prevGroupStyles === nextGroupStyles;
  if (!groupStylesEqual) {
    return false;
  }

  const messageEqual = checkMessageEquality(prevMessage, nextMessage);
  if (!messageEqual) {
    return false;
  }

  const quotedMessageEqual = checkQuotedMessageEquality(
    prevMessage.quoted_message,
    nextMessage.quoted_message,
  );

  if (!quotedMessageEqual) {
    return false;
  }

  const prevMessageAttachments = prevMessage.attachments;
  const nextMessageAttachments = nextMessage.attachments;
  const attachmentsEqual =
    Array.isArray(prevMessageAttachments) && Array.isArray(nextMessageAttachments)
      ? prevMessageAttachments.length === nextMessageAttachments.length &&
        prevMessageAttachments.every((attachment, index) => {
          const attachmentKeysEqual =
            attachment.image_url === nextMessageAttachments[index].image_url &&
            attachment.og_scrape_url === nextMessageAttachments[index].og_scrape_url &&
            attachment.thumb_url === nextMessageAttachments[index].thumb_url &&
            attachment.type === nextMessageAttachments[index].type;

          if (isAttachmentEqual) {
            return (
              attachmentKeysEqual && !!isAttachmentEqual(attachment, nextMessageAttachments[index])
            );
          }

          return attachmentKeysEqual;
        })
      : prevMessageAttachments === nextMessageAttachments;
  if (!attachmentsEqual) {
    return false;
  }

  const quotedMessageAttachmentsEqual =
    prevMessage.quoted_message?.attachments?.length ===
    nextMessage.quoted_message?.attachments?.length;

  if (!quotedMessageAttachmentsEqual) {
    return false;
  }

  const latestReactionsEqual =
    Array.isArray(prevMessage.latest_reactions) && Array.isArray(nextMessage.latest_reactions)
      ? prevMessage.latest_reactions.length === nextMessage.latest_reactions.length &&
        prevMessage.latest_reactions.every(
          ({ type }, index) => type === nextMessage.latest_reactions?.[index].type,
        )
      : prevMessage.latest_reactions === nextMessage.latest_reactions;
  if (!latestReactionsEqual) {
    return false;
  }

  const messageContentOrderEqual =
    prevMessageContentOrder.length === nextMessageContentOrder.length &&
    prevMessageContentOrder.every(
      (messageContentType, index) => messageContentType === nextMessageContentOrder[index],
    );
  if (!messageContentOrderEqual) {
    return false;
  }

  const tEqual = prevT === nextT;
  if (!tEqual) {
    return false;
  }

  const messageThemeEqual =
    JSON.stringify(prevMyMessageTheme) === JSON.stringify(nextMyMessageTheme);
  if (!messageThemeEqual) {
    return false;
  }

  const prevSharedLocation = prevMessage.shared_location;
  const nextSharedLocation = nextMessage.shared_location;
  const sharedLocationEqual =
    prevSharedLocation?.latitude === nextSharedLocation?.latitude &&
    prevSharedLocation?.longitude === nextSharedLocation?.longitude &&
    prevSharedLocation?.end_at === nextSharedLocation?.end_at;

  if (!sharedLocationEqual) {
    return false;
  }

  return true;
};

const MemoizedMessageContent = React.memo(
  MessageContentWithContext,
  areEqual,
) as typeof MessageContentWithContext;

export type MessageContentProps = Partial<MessageContentPropsWithContext>;

/**
 * Child of MessageItemView that displays a message's content
 */
export const MessageContent = (props: MessageContentProps) => {
  const {
    alignment,
    files,
    goToMessage,
    groupStyles,
    hasInteractiveAccessibilityContent,
    images,
    isMessageAIGenerated,
    isMyMessage,
    message,
    messageContentOrder,
    onLongPress,
    onPress,
    onPressIn,
    otherAttachments,
    preventPress,
    threadList,
    videos,
  } = useMessageContext();
  const {
    additionalPressableProps,
    enableMessageGroupingByUser,
    isAttachmentEqual,
    myMessageTheme,
  } = useMessagesContext();
  const { t } = useTranslationContext();
  const isSingleFile = files.length === 1;
  const messageHasPoll = messageContentOrder.includes('poll');
  const messageHasSingleMedia =
    messageContentOrder.length === 1 &&
    messageContentOrder.includes('gallery') &&
    images.length + videos.length === 1;
  const messageHasSingleFile =
    messageContentOrder.length === 1 && messageContentOrder[0] === 'files' && isSingleFile;
  const messageHasOnlyText = messageContentOrder.length === 1 && messageContentOrder[0] === 'text';
  const messageHasStandaloneGiphyOrImgur =
    !message.quoted_message &&
    otherAttachments.filter(
      (file) => file.type === FileTypes.Giphy || file.type === FileTypes.Imgur,
    ).length > 0;

  const hidePaddingTop =
    messageHasPoll ||
    messageHasSingleMedia ||
    messageHasSingleFile ||
    messageHasOnlyText ||
    messageHasStandaloneGiphyOrImgur;

  const hidePaddingHorizontal =
    messageHasPoll ||
    messageHasSingleMedia ||
    messageHasSingleFile ||
    messageHasStandaloneGiphyOrImgur;

  const hidePaddingBottom =
    messageHasPoll ||
    messageHasSingleMedia ||
    messageHasSingleFile ||
    messageHasOnlyText ||
    messageHasStandaloneGiphyOrImgur ||
    (messageContentOrder.length > 1 &&
      messageContentOrder[messageContentOrder.length - 1] === 'text');

  return (
    <MemoizedMessageContent
      {...{
        additionalPressableProps,
        alignment,
        enableMessageGroupingByUser,
        goToMessage,
        groupStyles,
        hasInteractiveAccessibilityContent,
        isAttachmentEqual,
        isMessageAIGenerated,
        isMyMessage,
        message,
        messageContentOrder,
        myMessageTheme,
        onLongPress,
        onPress,
        onPressIn,
        otherAttachments,
        preventPress,
        t,
        threadList,
        hidePaddingTop,
        hidePaddingHorizontal,
        hidePaddingBottom,
      }}
      {...props}
    />
  );
};

const styles = StyleSheet.create({
  container: {
    flexShrink: 1,
  },
  containerInner: {
    borderTopLeftRadius: components.messageBubbleRadiusGroupBottom,
    borderTopRightRadius: components.messageBubbleRadiusGroupBottom,
    overflow: 'hidden',
    borderWidth: 0,
  },
  contentBody: {
    flexShrink: 1,
    minWidth: 0,
  },
  contentRow: {
    flexDirection: 'row',
  },
  rightAlignContentRow: {
    flexDirection: 'row-reverse',
  },
  leftAlignContent: {
    justifyContent: 'flex-start',
  },
  leftAlignItems: {
    alignItems: 'flex-start',
  },
  replyBorder: {
    borderLeftWidth: 1,
    bottom: 0,
    position: 'absolute',
  },
  replyContainer: {
    alignSelf: 'center',
  },
  galleryContainer: {},
  noBorder: { borderWidth: 0 },
  rightAlignContent: {
    justifyContent: 'flex-end',
  },
  rightAlignItems: {
    alignItems: 'flex-end',
  },
  textWrapper: {},
});
