/**
 * DefaultMessage component is used to display a default message in the chat.
 * There's a workaround to fix wrong subcomponents when it renders inside a recycler view
 * by adding a `key` to some components.
 */
import React, {memo, useCallback, useRef, useState} from 'react';
import {
  Keyboard,
  LayoutChangeEvent,
  Pressable,
  PressableProps,
} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import * as Haptics from 'expo-haptics';
import {useShallow} from 'zustand/shallow';
import {TypographyWithLinks} from '@components';
import {
  buildMessageAccessibilityLabel,
  mapPendingAsset,
  mapUserFromMessage,
  MESSAGE_TEXT_STYLES,
} from '@modules/chats/utils';
import {
  BlockMessageReplied,
  BlockType,
  DefaultMessage,
} from '@modules/chats/interfaces';
import {useMessageActions} from '@modules/chats/contexts';
import {usePostMessage} from '@modules/chats/hooks';
import {useMessagesSearch} from '@modules/chats/store';
import {MessagesUpdaterService} from '@modules/chats/instances/updaters';
import {useTheme} from '@shared/theme';
import {Screens} from '@shared/constants';
import {isAndroid} from '@shared/utils';

import {MultimediaMessageDisplayer} from '../../screens/ChatMessages/components/MultimediaMessageDisplayer';
import {MessageItem} from '../MessageItem';
import GestureContainer from './components/GestureContainer';

interface Props {
  item: DefaultMessage;
  onPress?: () => void;
  pointerEvents?: PressableProps['pointerEvents'];
  scrollToMessage?: (msg: DefaultMessage) => (ts: string) => void;
  preparePlaylist?: (onReady?: () => void) => void;
}

let closeKeyboardTimer: Nullable<NodeJS.Timeout> = null;

function DefaultMessageComponent({
  item,
  onPress,
  pointerEvents,
  scrollToMessage,
  preparePlaylist,
}: Props) {
  const {theme} = useTheme();
  const navigation = useNavigation();
  const {text, client_msg_id, pendingAssets, channel} = item.data;
  const senderData = mapUserFromMessage(item.data);
  const {showFullMenu, showOnlyReactionsMenu} = useMessageActions();
  const {retrySending} = usePostMessage({channel});
  const replyBlock = item.data.blocks?.find(
    block => block.type === BlockType.MessageReplied,
  ) as BlockMessageReplied;
  const isForwarded = !!item.data.forwarded;
  const attachments = item.data.attachments?.length
    ? item.data.attachments
    : pendingAssets?.length
    ? pendingAssets.map(mapPendingAsset)
    : null;
  const showFooter =
    !attachments || attachments?.at(0)?.metadata.format !== 'audio';
  const textStyles = {
    ...MESSAGE_TEXT_STYLES,
    ...(isAndroid ? {maxHeight: item.data.height} : {}),
  };

  const timerRef = useRef<Nullable<NodeJS.Timeout>>(null);

  const textValue = text ? text.trim() : '';

  const accessibilityLabel = buildMessageAccessibilityLabel({
    senderData,
    isForwarded,
    replyBlock,
    attachments,
    text: item.data.formatted_text || item.data.text,
    ts: item.data.ts,
    status: item.data.status,
    edited: item.data.edited,
    deliver_status: item.data.deliver_status,
  });

  const onTagPress = (userId: number) => {
    navigation.navigate(Screens.PROFILE, {userId});
  };

  const onRetry = () => {
    retrySending(item.data);
  };

  const [isActiveLongPress, setIsActiveLongPress] = useState(false);

  const onGesture = useCallback(
    (
      e: {x: number; y: number; absoluteX: number; absoluteY: number},
      fullMenu = true,
    ) => {
      const top = e.absoluteY - e.y;

      if (fullMenu) {
        setIsActiveLongPress(true);

        setTimeout(() => {
          showFullMenu(item, top);
          setIsActiveLongPress(false);

          setTimeout(() => {
            // delay keyboard hiding to improve animation
            Keyboard.dismiss();
          }, 200);
        }, 200);
      } else {
        Keyboard.dismiss();
        showOnlyReactionsMenu(item, top);
      }
      // iOS handles it in native code, for Android we need to trigger it manually
      if (isAndroid) {
        Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
      }
    },
    [item, showFullMenu, showOnlyReactionsMenu],
  );

  const {query, matchedMessagesTsIndexMap} = useMessagesSearch(
    useShallow(state => ({
      query: state.query,
      matchedMessagesTsIndexMap: state.matchedMessagesTsIndexMap,
    })),
  );

  const onTypographyLayout = useCallback(
    (e: LayoutChangeEvent) => {
      const realHeight = e.nativeEvent.layout.height;
      // if the real msg height is bigger than the saved msg.height by 1px, update the msg.height
      if (realHeight - item.data.height > 1) {
        if (timerRef.current) {
          clearTimeout(timerRef.current);
        }
        // update with a delay ( to fix List layout issue )
        timerRef.current = setTimeout(() => {
          MessagesUpdaterService.updateMessageHeight(
            item.data.channel,
            item.data.client_msg_id,
            realHeight,
          );
        }, 50);
      }
    },
    [item?.data.channel, item?.data.client_msg_id, item?.data.height],
  );

  const onPressHandler = useCallback(() => {
    onPress?.();

    // Use a timeout to prevent double triggering on double tap action;(150ms feels similar to WhatsApp, when you press on a message)
    if (closeKeyboardTimer) {
      clearTimeout(closeKeyboardTimer);
      closeKeyboardTimer = null;
    }

    closeKeyboardTimer = setTimeout(() => {
      Keyboard.dismiss();
      closeKeyboardTimer = null;
    }, 150);
  }, [onPress]);

  const onScrollToMessageHandler = useCallback(
    (ts: string) => {
      scrollToMessage?.(item)(ts);
    },
    [item, scrollToMessage],
  );

  return (
    <GestureContainer
      onGesture={onGesture}
      // Ignore scroll indicator area
      gestureOffsetRight={30}>
      <Pressable
        onPress={onPressHandler}
        pointerEvents={pointerEvents}
        accessible={true}
        accessibilityLabel={accessibilityLabel}
        accessibilityRole="text">
        <MessageItem item={item}>
          <MessageItem.Row>
            <MessageItem.Avatar />
            <MessageItem.Bubble isActiveLongPress={isActiveLongPress}>
              {isForwarded ? (
                <MessageItem.ForwardedLabel />
              ) : replyBlock ? (
                <MessageItem.ReplyBubble
                  key={`${item.id}-reply-bubble`}
                  replyBlock={replyBlock}
                  scrollToMessage={
                    scrollToMessage ? onScrollToMessageHandler : undefined
                  }
                />
              ) : null}
              {attachments && (
                <MultimediaMessageDisplayer
                  itemId={item.id}
                  attachments={attachments}
                  messageId={client_msg_id}
                  channel={item.data.channel}
                  preparePlaylist={preparePlaylist}
                  senderData={senderData}
                />
              )}
              {!!textValue && (
                <TypographyWithLinks
                  variant="s"
                  searchQuery={
                    item.data.ts && matchedMessagesTsIndexMap.has(item.data.ts)
                      ? query
                      : undefined
                  }
                  color={theme.text.neutral.default}
                  allowFontScaling={false}
                  maxFontSizeMultiplier={100}
                  fontStyle="normal"
                  textBreakStrategy="simple"
                  style={textStyles}
                  value={textValue}
                  onTagPress={onTagPress}
                  highlightedSpans={item.data.highlighted_spans}
                  onNativeLayout={onTypographyLayout}
                />
              )}
              {showFooter && (
                <MessageItem.Footer key={`${item.data.ts}-footer`} />
              )}
            </MessageItem.Bubble>
          </MessageItem.Row>
          <MessageItem.ErrorMessage onRetry={onRetry} />
        </MessageItem>
      </Pressable>
    </GestureContainer>
  );
}

export default memo(DefaultMessageComponent, (prevProps, nextProps) => {
  return (
    prevProps.item.data.ts === nextProps.item.data.ts &&
    prevProps.item.data.height === nextProps.item.data.height &&
    prevProps.item.data.version === nextProps.item.data.version &&
    prevProps.item.data.text === nextProps.item.data.text &&
    prevProps.item.data.edited === nextProps.item.data.edited &&
    prevProps.item.showSenderInfo === nextProps.item.showSenderInfo &&
    prevProps.onPress === nextProps.onPress &&
    prevProps.item.data.status === nextProps.item.data.status &&
    prevProps.item.data.deliver_status?.read_at ===
      nextProps.item.data.deliver_status?.read_at &&
    prevProps.item.data.deliver_status?.received_at ===
      nextProps.item.data.deliver_status?.received_at
  );
});
