import React from 'react';
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';

import { LocalMessage } from 'stream-chat';

import { renderText, RenderTextParams } from './utils/renderText';

import { useComponentsContext } from '../../../contexts/componentsContext/ComponentsContext';
import {
  MessageContextValue,
  useMessageContext,
} from '../../../contexts/messageContext/MessageContext';
import {
  MessagesContextValue,
  useMessagesContext,
} from '../../../contexts/messagesContext/MessagesContext';
import { useTheme } from '../../../contexts/themeContext/ThemeContext';

import type { MarkdownStyle, Theme } from '../../../contexts/themeContext/utils/theme';
import { useTranslatedMessage } from '../../../hooks/useTranslatedMessage';
import { primitives } from '../../../theme';

const styles = StyleSheet.create({
  textContainer: { maxWidth: 256, paddingHorizontal: primitives.spacingXxs },
});

export type MessageTextProps = MessageTextContainerProps & {
  renderText: (params: RenderTextParams) => React.ReactNode | null;
  theme: { theme: Theme };
};

export type MessageTextContainerPropsWithContext = Pick<
  MessageContextValue,
  | 'hasInteractiveAccessibilityContent'
  | 'isMyMessage'
  | 'message'
  | 'onLongPress'
  | 'onlyEmojis'
  | 'onPress'
  | 'preventPress'
> &
  Pick<MessagesContextValue, 'markdownRules' | 'myMessageTheme' | 'messageTextNumberOfLines'> & {
    markdownStyles?: MarkdownStyle;
    messageOverlay?: boolean;
    styles?: Partial<{
      textContainer: StyleProp<ViewStyle>;
    }>;
  };

const MessageTextContainerWithContext = (props: MessageTextContainerPropsWithContext) => {
  const theme = useTheme();

  const {
    hasInteractiveAccessibilityContent,
    isMyMessage,
    markdownRules,
    markdownStyles: markdownStylesProp = {},
    message,
    messageOverlay,
    messageTextNumberOfLines,
    onLongPress,
    onlyEmojis,
    onPress,
    preventPress,
    styles: stylesProp = {},
  } = props;
  const { MessageText } = useComponentsContext();

  const {
    theme: {
      messageItemView: {
        content: {
          markdown,
          textContainer: { onlyEmojiMarkdown, ...textContainer },
        },
      },
      semantics,
    },
  } = theme;

  const translatedMessage = useTranslatedMessage(message);

  if (!message.text) {
    return null;
  }

  const markdownStyles = { ...markdown, ...markdownStylesProp };

  return (
    <View
      accessible={hasInteractiveAccessibilityContent || undefined}
      accessibilityRole={hasInteractiveAccessibilityContent ? 'text' : undefined}
      style={[styles.textContainer, textContainer, stylesProp.textContainer]}
      testID='message-text-container'
    >
      {MessageText ? (
        <MessageText {...props} renderText={renderText} theme={theme} />
      ) : (
        renderText({
          isMyMessage,
          semantics,
          markdownRules,
          markdownStyles: {
            ...markdownStyles,
            ...(onlyEmojis ? onlyEmojiMarkdown : {}),
          },
          message: translatedMessage as LocalMessage,
          messageOverlay,
          messageTextNumberOfLines,
          onLongPress,
          onlyEmojis,
          onPress,
          preventPress,
        })
      )}
    </View>
  );
};

const areEqual = (
  prevProps: MessageTextContainerPropsWithContext,
  nextProps: MessageTextContainerPropsWithContext,
) => {
  const {
    hasInteractiveAccessibilityContent: prevHasInteractiveAccessibilityContent,
    markdownStyles: prevMarkdownStyles,
    message: prevMessage,
    myMessageTheme: prevMyMessageTheme,
    onlyEmojis: prevOnlyEmojis,
  } = prevProps;
  const {
    hasInteractiveAccessibilityContent: nextHasInteractiveAccessibilityContent,
    markdownStyles: nextMarkdownStyles,
    message: nextMessage,
    myMessageTheme: nextMyMessageTheme,
    onlyEmojis: nextOnlyEmojis,
  } = nextProps;

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

  const messageStatusEqual = prevMessage.status === nextMessage.status;
  if (!messageStatusEqual) {
    return false;
  }

  const messageTextEqual =
    prevMessage.text === nextMessage.text && prevMessage.i18n === nextMessage.i18n;
  if (!messageTextEqual) {
    return false;
  }

  const onlyEmojisEqual = prevOnlyEmojis === nextOnlyEmojis;
  if (!onlyEmojisEqual) {
    return false;
  }

  const mentionedUsersEqual =
    prevMessage.mentioned_users?.length === nextMessage.mentioned_users?.length &&
    (nextMessage.mentioned_users?.length === 0 ||
      (prevMessage.mentioned_users?.length &&
        nextMessage.mentioned_users?.length &&
        prevMessage.mentioned_users[0].name === nextMessage.mentioned_users[0].name));
  if (!mentionedUsersEqual) {
    return false;
  }

  // Enhanced mention sources (channel/here/roles/groups) — without these the
  // renderText cache would refresh on a new entity but this comparator would
  // short-circuit it away and the highlight would not appear until something
  // else re-rendered the row.
  const mentionedBroadcastEqual =
    prevMessage.mentioned_channel === nextMessage.mentioned_channel &&
    prevMessage.mentioned_here === nextMessage.mentioned_here;
  if (!mentionedBroadcastEqual) {
    return false;
  }

  const joinIds = (values?: string[]) => (values ?? []).join('|');
  const mentionedRolesEqual =
    joinIds(prevMessage.mentioned_roles) === joinIds(nextMessage.mentioned_roles);
  if (!mentionedRolesEqual) {
    return false;
  }

  const groupIds = (m: typeof prevMessage) =>
    joinIds(m.mentioned_groups?.map((g) => g.id) ?? m.mentioned_group_ids);
  const mentionedGroupsEqual = groupIds(prevMessage) === groupIds(nextMessage);
  if (!mentionedGroupsEqual) {
    return false;
  }

  // stringify could be an expensive operation, so lets rule out the obvious
  // possibilities first such as different object reference or empty objects etc.
  // Also keeping markdown equality check at the last to make sure other less
  // expensive equality checks get executed first and markdown check will be skipped if returned
  // false from previous checks.
  const markdownStylesEqual =
    prevMarkdownStyles === nextMarkdownStyles ||
    (Object.keys(prevMarkdownStyles || {}).length === 0 &&
      Object.keys(nextMarkdownStyles || {}).length === 0) ||
    JSON.stringify(prevMarkdownStyles) === JSON.stringify(nextMarkdownStyles);
  if (!markdownStylesEqual) {
    return false;
  }

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

  return true;
};

const MemoizedMessageTextContainer = React.memo(
  MessageTextContainerWithContext,
  areEqual,
) as typeof MessageTextContainerWithContext;

export type MessageTextContainerProps = Partial<MessageTextContainerPropsWithContext>;

export const MessageTextContainer = (props: MessageTextContainerProps) => {
  const {
    hasInteractiveAccessibilityContent,
    isMyMessage,
    message,
    onLongPress,
    onlyEmojis,
    onPress,
    preventPress,
  } = useMessageContext();
  const { markdownRules, messageTextNumberOfLines, myMessageTheme } = useMessagesContext();

  return (
    <MemoizedMessageTextContainer
      {...{
        hasInteractiveAccessibilityContent,
        isMyMessage,
        markdownRules,
        message,
        messageTextNumberOfLines,
        myMessageTheme,
        onLongPress,
        onlyEmojis,
        onPress,
        preventPress,
      }}
      {...props}
    />
  );
};

MessageTextContainer.displayName = 'MessageTextContainer{messageItemView{content}}';
