/* eslint-disable react-hooks/rules-of-hooks */
import {
  Avatar,
  Box,
  Button,
  Dialog,
  Header,
  Icon,
  IconButton,
  Icons,
  Input,
  Line,
  Menu,
  MenuItem,
  Modal,
  Overlay,
  OverlayBackdrop,
  OverlayCenter,
  PopOut,
  RectCords,
  Spinner,
  Text,
  as,
  color,
  config,
} from 'folds';
import React, {
  FormEventHandler,
  MouseEventHandler,
  ReactNode,
  useCallback,
  useRef,
  useState,
} from 'react';
import FocusTrap from 'focus-trap-react';
import { useHover, useFocusWithin } from 'react-aria';
import { MatrixEvent, Room, EventStatus } from 'matrix-js-sdk';
import { Relations } from 'matrix-js-sdk/lib/models/relations';
import classNames from 'classnames';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
import {
  AvatarBase,
  BubbleLayout,
  CompactLayout,
  MessageBase,
  ModernLayout,
  Time,
  Username,
  UsernameBold,
} from '../../../components/message';
import {
  canEditEvent,
  getEventEdits,
  getMemberAvatarMxc,
  getMemberDisplayName,
} from '../../../utils/room';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
import { MessageLayout, MessageSpacing } from '../../../state/settings';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
import * as css from './styles.css';
import { MsgAppearClass, SendingSpinClass } from '../../../styles/Animations.css';
import { MentionHighlightPulse } from '../../../components/message/layout/layout.css';
import { EventReaders } from '../../../components/event-readers';
import { ReadReceiptAvatars } from '../../../components/read-receipt-avatars';
import { useReadPositions } from '../ReadPositionsContext';
import { TextViewer } from '../../../components/text-viewer';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { EmojiBoard } from '../../../components/emoji-board';
import { ReactionViewer } from '../reaction-viewer';
import { MessageEditor } from './MessageEditor';
import { UserAvatar } from '../../../components/user-avatar';
import { copyToClipboard } from '../../../utils/dom';
import { stopPropagation } from '../../../utils/keyboard';
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
import { getViaServers } from '../../../plugins/via-servers';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useRoomPinnedEvents } from '../../../hooks/useRoomPinnedEvents';
import { MemberPowerTag, StateEvent } from '../../../../types/matrix/room';
import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
import { ForwardMessageDialog } from './ForwardMessageDialog';
import { useBookmarks } from '../../../hooks/useBookmarks';
import { PresenceRingAvatar } from '../../../components/presence';
import { AvatarDecoration } from '../../../components/avatar-decoration/AvatarDecoration';

// Delivery status indicator for own messages
function DeliveryStatus({
  status,
  lotusTerminal,
}: {
  status: string | null;
  lotusTerminal: boolean;
}) {
  if (status === null) return null; // confirmed by server — read receipts take over
  let icon: string;
  let label: string;
  let colorStyle: string;
  if (status === EventStatus.NOT_SENT || status === EventStatus.CANCELLED) {
    icon = '✕';
    label = 'Failed to send';
    colorStyle = lotusTerminal ? '#FF3B3B' : color.Critical.Main;
  } else if (status === EventStatus.QUEUED) {
    icon = '⟳';
    label = 'Queued';
    colorStyle = lotusTerminal ? 'rgba(0,212,255,0.45)' : color.Secondary.Main;
  } else if (status === EventStatus.SENDING || status === EventStatus.ENCRYPTING) {
    icon = '⟳';
    label = 'Sending...';
    colorStyle = lotusTerminal ? 'rgba(0,212,255,0.60)' : color.Secondary.Main;
  } else {
    icon = '✓';
    label = 'Sent';
    colorStyle = lotusTerminal ? 'rgba(0,212,255,0.70)' : color.Secondary.Main;
  }
  return (
    <Box
      as="span"
      aria-label={label}
      title={label}
      style={{
        display: 'inline-flex',
        alignItems: 'center',
        marginTop: '2px',
        fontSize: '10px',
        lineHeight: 1,
        color: colorStyle,
        opacity: 0.85,
        userSelect: 'none',
        ...(lotusTerminal && status === EventStatus.NOT_SENT
          ? { textShadow: '0 0 6px #FF3B3B' }
          : {}),
      }}
    >
      <span
        className={
          status === EventStatus.SENDING || status === EventStatus.ENCRYPTING
            ? SendingSpinClass
            : undefined
        }
      >
        {icon}
      </span>
    </Box>
  );
}

export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;

type MessageQuickReactionsProps = {
  onReaction: ReactionHandler;
};
export const MessageQuickReactions = as<'div', MessageQuickReactionsProps>(
  ({ onReaction, ...props }, ref) => {
    const mx = useMatrixClient();
    const recentEmojis = useRecentEmoji(mx, 3);

    if (recentEmojis.length === 0) return <span />;
    return (
      <>
        <Box
          style={{ padding: config.space.S200 }}
          alignItems="Center"
          justifyContent="Center"
          gap="200"
          {...props}
          ref={ref}
        >
          {recentEmojis.map((emoji) => (
            <IconButton
              key={emoji.unicode}
              className={css.MessageQuickReaction}
              size="300"
              variant="SurfaceVariant"
              radii="Pill"
              title={emoji.shortcode}
              aria-label={emoji.shortcode}
              onClick={() => onReaction(emoji.unicode, emoji.shortcode)}
            >
              <Text size="T500">{emoji.unicode}</Text>
            </IconButton>
          ))}
        </Box>
      </>
    );
  },
);

export const MessageAllReactionItem = as<
  'button',
  {
    room: Room;
    relations: Relations;
    onClose?: () => void;
  }
>(({ room, relations, onClose, ...props }, ref) => {
  const [open, setOpen] = useState(false);

  const handleClose = () => {
    setOpen(false);
    onClose?.();
  };

  return (
    <>
      <Overlay
        onContextMenu={(evt: any) => {
          evt.stopPropagation();
        }}
        open={open}
        backdrop={<OverlayBackdrop />}
      >
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              returnFocusOnDeactivate: false,
              onDeactivate: () => handleClose(),
              clickOutsideDeactivates: true,
              escapeDeactivates: stopPropagation,
            }}
          >
            <Modal variant="Surface" size="300">
              <ReactionViewer
                room={room}
                relations={relations}
                requestClose={() => setOpen(false)}
              />
            </Modal>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      <MenuItem
        size="300"
        after={<Icon size="100" src={Icons.Smile} />}
        radii="300"
        onClick={() => setOpen(true)}
        {...props}
        ref={ref}
        aria-pressed={open}
      >
        <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
          View Reactions
        </Text>
      </MenuItem>
    </>
  );
});

export const MessageReadReceiptItem = as<
  'button',
  {
    room: Room;
    eventId: string;
    onClose?: () => void;
  }
>(({ room, eventId, onClose, ...props }, ref) => {
  const [open, setOpen] = useState(false);

  const handleClose = () => {
    setOpen(false);
    onClose?.();
  };

  return (
    <>
      <Overlay open={open} backdrop={<OverlayBackdrop />}>
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              onDeactivate: handleClose,
              clickOutsideDeactivates: true,
              escapeDeactivates: stopPropagation,
            }}
          >
            <Modal variant="Surface" size="300">
              <EventReaders room={room} eventId={eventId} requestClose={handleClose} />
            </Modal>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      <MenuItem
        size="300"
        after={<Icon size="100" src={Icons.CheckTwice} />}
        radii="300"
        onClick={() => setOpen(true)}
        {...props}
        ref={ref}
        aria-pressed={open}
      >
        <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
          Read Receipts
        </Text>
      </MenuItem>
    </>
  );
});

export const MessageSourceCodeItem = as<
  'button',
  {
    room: Room;
    mEvent: MatrixEvent;
    onClose?: () => void;
  }
>(({ room, mEvent, onClose, ...props }, ref) => {
  const [open, setOpen] = useState(false);

  const getContent = (evt: MatrixEvent) =>
    evt.isEncrypted()
      ? {
          [`<== DECRYPTED_EVENT ==>`]: evt.getEffectiveEvent(),
          [`<== ORIGINAL_EVENT ==>`]: evt.event,
        }
      : evt.event;

  const getText = (): string => {
    const evtId = mEvent.getId()!;
    const evtTimeline = room.getTimelineForEvent(evtId);
    const edits =
      evtTimeline &&
      getEventEdits(evtTimeline.getTimelineSet(), evtId, mEvent.getType())?.getRelations();

    if (!edits) return JSON.stringify(getContent(mEvent), null, 2);

    const content: Record<string, unknown> = {
      '<== MAIN_EVENT ==>': getContent(mEvent),
    };

    edits.forEach((editEvt, index) => {
      content[`<== REPLACEMENT_EVENT_${index + 1} ==>`] = getContent(editEvt);
    });

    return JSON.stringify(content, null, 2);
  };

  const handleClose = () => {
    setOpen(false);
    onClose?.();
  };

  return (
    <>
      <Overlay open={open} backdrop={<OverlayBackdrop />}>
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              onDeactivate: handleClose,
              clickOutsideDeactivates: true,
              escapeDeactivates: stopPropagation,
            }}
          >
            <Modal variant="Surface" size="500">
              <TextViewer
                name="Source Code"
                langName="json"
                text={getText()}
                requestClose={handleClose}
              />
            </Modal>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      <MenuItem
        size="300"
        after={<Icon size="100" src={Icons.BlockCode} />}
        radii="300"
        onClick={() => setOpen(true)}
        {...props}
        ref={ref}
        aria-pressed={open}
      >
        <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
          View Source
        </Text>
      </MenuItem>
    </>
  );
});

export const MessageCopyLinkItem = as<
  'button',
  {
    room: Room;
    mEvent: MatrixEvent;
    onClose?: () => void;
  }
>(({ room, mEvent, onClose, ...props }, ref) => {
  const handleCopy = () => {
    const eventId = mEvent.getId();
    if (!eventId) return;
    copyToClipboard(getMatrixToRoomEvent(room.roomId, eventId, getViaServers(room)));
    onClose?.();
  };

  return (
    <MenuItem
      size="300"
      after={<Icon size="100" src={Icons.Link} />}
      radii="300"
      onClick={handleCopy}
      {...props}
      ref={ref}
    >
      <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
        Copy Link
      </Text>
    </MenuItem>
  );
});

export const MessagePinItem = as<
  'button',
  {
    room: Room;
    mEvent: MatrixEvent;
    onClose?: () => void;
  }
>(({ room, mEvent, onClose, ...props }, ref) => {
  const mx = useMatrixClient();
  const pinnedEvents = useRoomPinnedEvents(room);
  const isPinned = pinnedEvents.includes(mEvent.getId() ?? '');
  const handlePin = () => {
    const eventId = mEvent.getId();
    const pinContent: RoomPinnedEventsEventContent = {
      pinned: Array.from(pinnedEvents).filter((id) => id !== eventId),
    };
    if (!isPinned && eventId) {
      pinContent.pinned.push(eventId);
    }
    mx.sendStateEvent(room.roomId, StateEvent.RoomPinnedEvents as any, pinContent);
    onClose?.();
  };

  return (
    <MenuItem
      size="300"
      after={<Icon size="100" src={Icons.Pin} />}
      radii="300"
      onClick={handlePin}
      {...props}
      ref={ref}
    >
      <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
        {isPinned ? 'Unpin Message' : 'Pin Message'}
      </Text>
    </MenuItem>
  );
});

export const MessageDeleteItem = as<
  'button',
  {
    room: Room;
    mEvent: MatrixEvent;
    onClose?: () => void;
  }
>(({ room, mEvent, onClose, ...props }, ref) => {
  const mx = useMatrixClient();
  const [open, setOpen] = useState(false);
  const [deleteState, deleteMessage] = useAsyncCallback(
    useCallback(
      (eventId: string, reason?: string) =>
        mx.redactEvent(room.roomId, eventId, undefined, reason ? { reason } : undefined),
      [mx, room],
    ),
  );

  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
    evt.preventDefault();
    const eventId = mEvent.getId();
    if (
      !eventId ||
      deleteState.status === AsyncStatus.Loading ||
      deleteState.status === AsyncStatus.Success
    )
      return;
    const target = evt.target as HTMLFormElement | undefined;
    const reasonInput = target?.reasonInput as HTMLInputElement | undefined;
    const reason = reasonInput && reasonInput.value.trim();
    deleteMessage(eventId, reason);
  };

  const handleClose = () => {
    setOpen(false);
    onClose?.();
  };

  return (
    <>
      <Overlay open={open} backdrop={<OverlayBackdrop />}>
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              onDeactivate: handleClose,
              clickOutsideDeactivates: true,
              escapeDeactivates: stopPropagation,
            }}
          >
            <Dialog variant="Surface">
              <Header
                style={{
                  padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                  borderBottomWidth: config.borderWidth.B300,
                }}
                variant="Surface"
                size="500"
              >
                <Box grow="Yes">
                  <Text size="H4">Delete Message</Text>
                </Box>
                <IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
                  <Icon src={Icons.Cross} />
                </IconButton>
              </Header>
              <Box
                as="form"
                onSubmit={handleSubmit}
                style={{ padding: config.space.S400 }}
                direction="Column"
                gap="400"
              >
                <Text priority="400">
                  This action is irreversible! Are you sure that you want to delete this message?
                </Text>
                <Box direction="Column" gap="100">
                  <Text size="L400">
                    Reason{' '}
                    <Text as="span" size="T200">
                      (optional)
                    </Text>
                  </Text>
                  <Input name="reasonInput" variant="Background" />
                  {deleteState.status === AsyncStatus.Error && (
                    <Text style={{ color: color.Critical.Main }} size="T300">
                      Failed to delete message! Please try again.
                    </Text>
                  )}
                </Box>
                <Button
                  type="submit"
                  variant="Critical"
                  before={
                    deleteState.status === AsyncStatus.Loading ? (
                      <Spinner fill="Solid" variant="Critical" size="200" />
                    ) : undefined
                  }
                  aria-disabled={deleteState.status === AsyncStatus.Loading}
                >
                  <Text size="B400">
                    {deleteState.status === AsyncStatus.Loading ? 'Deleting...' : 'Delete'}
                  </Text>
                </Button>
              </Box>
            </Dialog>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      <Button
        variant="Critical"
        fill="None"
        size="300"
        after={<Icon size="100" src={Icons.Delete} />}
        radii="300"
        onClick={() => setOpen(true)}
        aria-pressed={open}
        {...props}
        ref={ref}
      >
        <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
          Delete
        </Text>
      </Button>
    </>
  );
});

export const MessageReportItem = as<
  'button',
  {
    room: Room;
    mEvent: MatrixEvent;
    onClose?: () => void;
  }
>(({ room, mEvent, onClose, ...props }, ref) => {
  const mx = useMatrixClient();
  const [open, setOpen] = useState(false);
  const [reportState, reportMessage] = useAsyncCallback(
    useCallback(
      (eventId: string, score: number, reason: string) =>
        mx.reportEvent(room.roomId, eventId, score, reason),
      [mx, room],
    ),
  );

  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
    evt.preventDefault();
    const eventId = mEvent.getId();
    if (
      !eventId ||
      reportState.status === AsyncStatus.Loading ||
      reportState.status === AsyncStatus.Success
    )
      return;
    const target = evt.target as HTMLFormElement | undefined;
    const reasonInput = target?.reasonInput as HTMLInputElement | undefined;
    const reason = reasonInput && reasonInput.value.trim();
    if (reasonInput) reasonInput.value = '';
    reportMessage(eventId, reason ? -100 : -50, reason || 'No reason provided');
  };

  const handleClose = () => {
    setOpen(false);
    onClose?.();
  };

  return (
    <>
      <Overlay open={open} backdrop={<OverlayBackdrop />}>
        <OverlayCenter>
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              onDeactivate: handleClose,
              clickOutsideDeactivates: true,
              escapeDeactivates: stopPropagation,
            }}
          >
            <Dialog variant="Surface">
              <Header
                style={{
                  padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                  borderBottomWidth: config.borderWidth.B300,
                }}
                variant="Surface"
                size="500"
              >
                <Box grow="Yes">
                  <Text size="H4">Report Message</Text>
                </Box>
                <IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
                  <Icon src={Icons.Cross} />
                </IconButton>
              </Header>
              <Box
                as="form"
                onSubmit={handleSubmit}
                style={{ padding: config.space.S400 }}
                direction="Column"
                gap="400"
              >
                <Text priority="400">
                  Report this message to server, which may then notify the appropriate people to
                  take action.
                </Text>
                <Box direction="Column" gap="100">
                  <Text size="L400">Reason</Text>
                  <Input name="reasonInput" variant="Background" required />
                  {reportState.status === AsyncStatus.Error && (
                    <Text style={{ color: color.Critical.Main }} size="T300">
                      Failed to report message! Please try again.
                    </Text>
                  )}
                  {reportState.status === AsyncStatus.Success && (
                    <Text style={{ color: color.Success.Main }} size="T300">
                      Message has been reported to server.
                    </Text>
                  )}
                </Box>
                <Button
                  type="submit"
                  variant="Critical"
                  before={
                    reportState.status === AsyncStatus.Loading ? (
                      <Spinner fill="Solid" variant="Critical" size="200" />
                    ) : undefined
                  }
                  aria-disabled={
                    reportState.status === AsyncStatus.Loading ||
                    reportState.status === AsyncStatus.Success
                  }
                >
                  <Text size="B400">
                    {reportState.status === AsyncStatus.Loading ? 'Reporting...' : 'Report'}
                  </Text>
                </Button>
              </Box>
            </Dialog>
          </FocusTrap>
        </OverlayCenter>
      </Overlay>
      <Button
        variant="Critical"
        fill="None"
        size="300"
        after={<Icon size="100" src={Icons.Warning} />}
        radii="300"
        onClick={() => setOpen(true)}
        aria-pressed={open}
        {...props}
        ref={ref}
      >
        <Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
          Report
        </Text>
      </Button>
    </>
  );
});

export type MessageProps = {
  room: Room;
  mEvent: MatrixEvent;
  collapse: boolean;
  highlight: boolean;
  edit?: boolean;
  canDelete?: boolean;
  canSendReaction?: boolean;
  canPinEvent?: boolean;
  imagePackRooms?: Room[];
  relations?: Relations;
  messageLayout: MessageLayout;
  messageSpacing: MessageSpacing;
  onUserClick: MouseEventHandler<HTMLButtonElement>;
  onUsernameClick: MouseEventHandler<HTMLButtonElement>;
  onReplyClick: (
    ev: Parameters<MouseEventHandler<HTMLButtonElement>>[0],
    startThread?: boolean,
  ) => void;
  onEditId?: (eventId?: string) => void;
  onReactionToggle: (targetEventId: string, key: string, shortcode?: string) => void;
  reply?: ReactNode;
  reactions?: ReactNode;
  hideReadReceipts?: boolean;
  showDeveloperTools?: boolean;
  memberPowerTag?: MemberPowerTag;
  accessibleTagColors?: Map<string, string>;
  legacyUsernameColor?: boolean;
  hour24Clock: boolean;
  dateFormatString: string;
  lotusTerminal?: boolean;
};
export const Message = React.memo(
  as<'div', MessageProps>(
    (
      {
        className,
        room,
        mEvent,
        collapse,
        highlight,
        edit,
        canDelete,
        canSendReaction,
        canPinEvent,
        imagePackRooms,
        relations,
        messageLayout,
        messageSpacing,
        onUserClick,
        onUsernameClick,
        onReplyClick,
        onReactionToggle,
        onEditId,
        reply,
        reactions,
        hideReadReceipts,
        showDeveloperTools,
        memberPowerTag,
        accessibleTagColors,
        legacyUsernameColor,
        hour24Clock,
        dateFormatString,
        lotusTerminal: lotusTerminalProp,
        children,
        ...props
      },
      ref,
    ) => {
      const mx = useMatrixClient();
      const useAuthentication = useMediaAuthentication();
      const senderId = mEvent.getSender() ?? '';
      const readPositions = useReadPositions();
      const readReceiptUsers = hideReadReceipts
        ? []
        : (readPositions.get(mEvent.getId() ?? '') ?? []);
      const isMine = mEvent.getSender() === mx.getUserId();
      const lotusTerminal = lotusTerminalProp;
      // Track whether this message should play the appear animation (own messages only)
      const isNewRef = useRef(true);
      const [playAppear, setPlayAppear] = useState(isMine && isNewRef.current);
      // Mention pulse: play once for new incoming @mention messages from others
      const myUserId = mx.getUserId() ?? '';
      const mentionContent = mEvent.getContent<{
        'm.mentions'?: { user_ids?: string[]; room?: boolean };
      }>();
      const isMentioned =
        !isMine &&
        (mentionContent['m.mentions']?.user_ids?.includes(myUserId) === true ||
          mentionContent['m.mentions']?.room === true);
      const [playMentionPulse, setPlayMentionPulse] = useState(isMentioned && isNewRef.current);
      const [hover, setHover] = useState(false);
      const { hoverProps } = useHover({ onHoverChange: setHover });
      const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
      const [menuAnchor, setMenuAnchor] = useState<RectCords>();
      const [emojiBoardAnchor, setEmojiBoardAnchor] = useState<RectCords>();
      const [forwardOpen, setForwardOpen] = useState(false);
      const { addBookmark, removeBookmark, isBookmarked } = useBookmarks();

      const senderDisplayName =
        getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
      const senderAvatarMxc = getMemberAvatarMxc(room, senderId);

      const tagColor = memberPowerTag?.color
        ? accessibleTagColors?.get(memberPowerTag.color)
        : undefined;
      const tagIconSrc = memberPowerTag?.icon
        ? getPowerTagIconSrc(mx, useAuthentication, memberPowerTag.icon)
        : undefined;

      const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor;

      const headerJSX = !collapse && (
        <Box
          gap="300"
          direction={messageLayout === MessageLayout.Compact ? 'RowReverse' : 'Row'}
          justifyContent="SpaceBetween"
          alignItems="Baseline"
          grow="Yes"
        >
          <Box alignItems="Center" gap="200">
            <Username
              as="button"
              style={{ color: usernameColor }}
              data-user-id={senderId}
              onContextMenu={onUserClick}
              onClick={onUsernameClick}
            >
              <Text
                as="span"
                size={messageLayout === MessageLayout.Bubble ? 'T300' : 'T400'}
                truncate
              >
                <UsernameBold>{senderDisplayName}</UsernameBold>
              </Text>
            </Username>
            {tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
          </Box>
          <Box shrink="No" gap="100">
            {messageLayout === MessageLayout.Modern && hover && (
              <>
                <Text as="span" size="T200" priority="300">
                  {senderId}
                </Text>
                <Text as="span" size="T200" priority="300">
                  |
                </Text>
              </>
            )}
            <Time
              ts={mEvent.getTs()}
              compact={messageLayout === MessageLayout.Compact}
              hour24Clock={hour24Clock}
              dateFormatString={dateFormatString}
            />
          </Box>
        </Box>
      );

      const avatarJSX = !collapse && messageLayout !== MessageLayout.Compact && (
        <AvatarBase
          className={messageLayout === MessageLayout.Bubble ? css.BubbleAvatarBase : undefined}
        >
          <AvatarDecoration userId={senderId}>
            <PresenceRingAvatar userId={senderId}>
              <Avatar
                className={css.MessageAvatar}
                as="button"
                size="300"
                data-user-id={senderId}
                onClick={onUserClick}
              >
                <UserAvatar
                  userId={senderId}
                  src={
                    senderAvatarMxc
                      ? (mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ??
                        undefined)
                      : undefined
                  }
                  alt={senderDisplayName}
                  renderFallback={() => <Icon size="200" src={Icons.User} filled />}
                />
              </Avatar>
            </PresenceRingAvatar>
          </AvatarDecoration>
        </AvatarBase>
      );

      const msgContentJSX = (
        <Box direction="Column" alignSelf="Start" style={{ maxWidth: '100%' }}>
          {reply}
          {edit && onEditId ? (
            <MessageEditor
              style={{
                maxWidth: '100%',
                width: '100vw',
              }}
              roomId={room.roomId}
              room={room}
              mEvent={mEvent}
              imagePackRooms={imagePackRooms}
              onCancel={() => onEditId()}
            />
          ) : (
            children
          )}
          {reactions}
          {readReceiptUsers.length > 0 && (
            <ReadReceiptAvatars
              room={room}
              eventId={mEvent.getId() ?? ''}
              userIds={readReceiptUsers}
            />
          )}
          {isMine && !mEvent.isState() && readReceiptUsers.length === 0 && (
            <DeliveryStatus status={mEvent.status} lotusTerminal={!!lotusTerminal} />
          )}
        </Box>
      );

      const handleContextMenu: MouseEventHandler<HTMLDivElement> = (evt) => {
        if (evt.altKey || !window.getSelection()?.isCollapsed || edit) return;
        const tag = (evt.target as any).tagName;
        if (typeof tag === 'string' && tag.toLowerCase() === 'a') return;
        evt.preventDefault();
        setMenuAnchor({
          x: evt.clientX,
          y: evt.clientY,
          width: 0,
          height: 0,
        });
      };

      const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
        const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
        setMenuAnchor(target.getBoundingClientRect());
      };

      const closeMenu = () => {
        setMenuAnchor(undefined);
      };

      const handleOpenEmojiBoard: MouseEventHandler<HTMLButtonElement> = (evt) => {
        const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
        setEmojiBoardAnchor(target.getBoundingClientRect());
      };
      const handleAddReactions: MouseEventHandler<HTMLButtonElement> = () => {
        const rect = menuAnchor;
        closeMenu();
        // open it with timeout because closeMenu
        // FocusTrap will return focus from emojiBoard

        setTimeout(() => {
          setEmojiBoardAnchor(rect);
        }, 100);
      };

      const isThreadedMessage = mEvent.threadRootId !== undefined;

      return (
        <MessageBase
          className={classNames(css.MessageBase, className, {
            [css.MessageBaseBubbleCollapsed]: messageLayout === MessageLayout.Bubble && collapse,
            [MsgAppearClass]: playAppear,
            [MentionHighlightPulse]: playMentionPulse,
          })}
          tabIndex={0}
          space={messageSpacing}
          collapse={collapse}
          highlight={highlight}
          selected={!!menuAnchor || !!emojiBoardAnchor}
          onAnimationEnd={() => {
            if (playAppear) {
              isNewRef.current = false;
              setPlayAppear(false);
            }
            if (playMentionPulse) {
              isNewRef.current = false;
              setPlayMentionPulse(false);
            }
          }}
          {...props}
          {...hoverProps}
          {...focusWithinProps}
          ref={ref}
        >
          {!edit && (hover || !!menuAnchor || !!emojiBoardAnchor) && (
            <div className={css.MessageOptionsBase}>
              <Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
                <Box gap="100">
                  {canSendReaction && (
                    <PopOut
                      position="Bottom"
                      align={emojiBoardAnchor?.width === 0 ? 'Start' : 'End'}
                      offset={emojiBoardAnchor?.width === 0 ? 0 : undefined}
                      anchor={emojiBoardAnchor}
                      content={
                        <EmojiBoard
                          imagePackRooms={imagePackRooms ?? []}
                          returnFocusOnDeactivate={false}
                          allowTextCustomEmoji
                          onEmojiSelect={(key) => {
                            onReactionToggle(mEvent.getId()!, key);
                            setEmojiBoardAnchor(undefined);
                          }}
                          onCustomEmojiSelect={(mxc, shortcode) => {
                            onReactionToggle(mEvent.getId()!, mxc, shortcode);
                            setEmojiBoardAnchor(undefined);
                          }}
                          requestClose={() => {
                            setEmojiBoardAnchor(undefined);
                          }}
                        />
                      }
                    >
                      <IconButton
                        onClick={handleOpenEmojiBoard}
                        variant="SurfaceVariant"
                        size="300"
                        radii="300"
                        aria-label="Add reaction"
                        aria-pressed={!!emojiBoardAnchor}
                      >
                        <Icon src={Icons.SmilePlus} size="100" />
                      </IconButton>
                    </PopOut>
                  )}
                  {canSendReaction && (
                    <MessageQuickReactions
                      onReaction={(key, shortcode) => {
                        onReactionToggle(mEvent.getId()!, key, shortcode);
                        setEmojiBoardAnchor(undefined);
                      }}
                    />
                  )}
                  <IconButton
                    onClick={onReplyClick}
                    data-event-id={mEvent.getId()}
                    variant="SurfaceVariant"
                    size="300"
                    radii="300"
                    aria-label="Reply"
                  >
                    <Icon src={Icons.ReplyArrow} size="100" />
                  </IconButton>
                  {!isThreadedMessage && (
                    <IconButton
                      onClick={(ev) => onReplyClick(ev, true)}
                      data-event-id={mEvent.getId()}
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                      aria-label="Reply in thread"
                    >
                      <Icon src={Icons.ThreadPlus} size="100" />
                    </IconButton>
                  )}
                  {canEditEvent(mx, mEvent) && onEditId && (
                    <IconButton
                      onClick={() => onEditId(mEvent.getId())}
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                      aria-label="Edit message"
                    >
                      <Icon src={Icons.Pencil} size="100" />
                    </IconButton>
                  )}
                  <PopOut
                    anchor={menuAnchor}
                    position="Bottom"
                    align={menuAnchor?.width === 0 ? 'Start' : 'End'}
                    offset={menuAnchor?.width === 0 ? 0 : undefined}
                    content={
                      <FocusTrap
                        focusTrapOptions={{
                          initialFocus: false,
                          onDeactivate: () => setMenuAnchor(undefined),
                          clickOutsideDeactivates: true,
                          isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
                          isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
                          escapeDeactivates: stopPropagation,
                        }}
                      >
                        <Menu>
                          <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
                            {canSendReaction && (
                              <MenuItem
                                size="300"
                                after={<Icon size="100" src={Icons.SmilePlus} />}
                                radii="300"
                                onClick={handleAddReactions}
                              >
                                <Text
                                  className={css.MessageMenuItemText}
                                  as="span"
                                  size="T300"
                                  truncate
                                >
                                  Add Reaction
                                </Text>
                              </MenuItem>
                            )}
                            {relations && (
                              <MessageAllReactionItem
                                room={room}
                                relations={relations}
                                onClose={closeMenu}
                              />
                            )}
                            <MenuItem
                              size="300"
                              after={<Icon size="100" src={Icons.ReplyArrow} />}
                              radii="300"
                              data-event-id={mEvent.getId()}
                              onClick={(evt: any) => {
                                onReplyClick(evt);
                                closeMenu();
                              }}
                            >
                              <Text
                                className={css.MessageMenuItemText}
                                as="span"
                                size="T300"
                                truncate
                              >
                                Reply
                              </Text>
                            </MenuItem>
                            {!mEvent.isRedacted() && (
                              <MenuItem
                                size="300"
                                after={<Icon src={Icons.ArrowRight} />}
                                radii="300"
                                onClick={() => {
                                  setForwardOpen(true);
                                  closeMenu();
                                }}
                              >
                                <Text
                                  className={css.MessageMenuItemText}
                                  as="span"
                                  size="T300"
                                  truncate
                                >
                                  Forward
                                </Text>
                              </MenuItem>
                            )}
                            {!mEvent.isRedacted() && mEvent.getId() && (
                              <MenuItem
                                size="300"
                                after={
                                  <Icon
                                    size="100"
                                    src={Icons.Star}
                                    filled={isBookmarked(mEvent.getId()!)}
                                  />
                                }
                                radii="300"
                                onClick={() => {
                                  const eventId = mEvent.getId()!;
                                  if (isBookmarked(eventId)) {
                                    removeBookmark(eventId);
                                  } else {
                                    const content = mEvent.getContent();
                                    const body: string =
                                      (content?.body as string | undefined) ?? '';
                                    addBookmark({
                                      roomId: room.roomId,
                                      eventId,
                                      savedAt: Date.now(),
                                      previewText: body.slice(0, 120),
                                      roomName: room.name,
                                    });
                                  }
                                  closeMenu();
                                }}
                              >
                                <Text
                                  className={css.MessageMenuItemText}
                                  as="span"
                                  size="T300"
                                  truncate
                                >
                                  {isBookmarked(mEvent.getId()!)
                                    ? 'Remove Bookmark'
                                    : 'Bookmark Message'}
                                </Text>
                              </MenuItem>
                            )}
                            {!isThreadedMessage && (
                              <MenuItem
                                size="300"
                                after={<Icon src={Icons.ThreadPlus} size="100" />}
                                radii="300"
                                data-event-id={mEvent.getId()}
                                onClick={(evt: any) => {
                                  onReplyClick(evt, true);
                                  closeMenu();
                                }}
                              >
                                <Text
                                  className={css.MessageMenuItemText}
                                  as="span"
                                  size="T300"
                                  truncate
                                >
                                  Reply in Thread
                                </Text>
                              </MenuItem>
                            )}
                            {canEditEvent(mx, mEvent) && onEditId && (
                              <MenuItem
                                size="300"
                                after={<Icon size="100" src={Icons.Pencil} />}
                                radii="300"
                                data-event-id={mEvent.getId()}
                                onClick={() => {
                                  onEditId(mEvent.getId());
                                  closeMenu();
                                }}
                              >
                                <Text
                                  className={css.MessageMenuItemText}
                                  as="span"
                                  size="T300"
                                  truncate
                                >
                                  Edit Message
                                </Text>
                              </MenuItem>
                            )}
                            {!hideReadReceipts && (
                              <MessageReadReceiptItem
                                room={room}
                                eventId={mEvent.getId() ?? ''}
                                onClose={closeMenu}
                              />
                            )}
                            {showDeveloperTools && (
                              <MessageSourceCodeItem
                                room={room}
                                mEvent={mEvent}
                                onClose={closeMenu}
                              />
                            )}
                            <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
                            {canPinEvent && (
                              <MessagePinItem room={room} mEvent={mEvent} onClose={closeMenu} />
                            )}
                          </Box>
                          {(mEvent.status === EventStatus.NOT_SENT ||
                            mEvent.status === EventStatus.CANCELLED) && (
                            <>
                              <Line size="300" />
                              <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
                                <MenuItem
                                  size="300"
                                  after={<Icon size="100" src={Icons.Send} />}
                                  radii="300"
                                  onClick={() => {
                                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                    (mx as any).resendEvent(mEvent, room);
                                    closeMenu();
                                  }}
                                >
                                  <Text
                                    className={css.MessageMenuItemText}
                                    as="span"
                                    size="T300"
                                    truncate
                                  >
                                    Retry Send
                                  </Text>
                                </MenuItem>
                                <MenuItem
                                  size="300"
                                  after={<Icon size="100" src={Icons.Cross} />}
                                  radii="300"
                                  onClick={() => {
                                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                                    (mx as any).cancelPendingEvent(mEvent);
                                    closeMenu();
                                  }}
                                >
                                  <Text
                                    className={css.MessageMenuItemText}
                                    as="span"
                                    size="T300"
                                    truncate
                                  >
                                    Cancel Message
                                  </Text>
                                </MenuItem>
                              </Box>
                            </>
                          )}
                          {((!mEvent.isRedacted() && canDelete) ||
                            mEvent.getSender() !== mx.getUserId()) && (
                            <>
                              <Line size="300" />
                              <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
                                {!mEvent.isRedacted() && canDelete && (
                                  <MessageDeleteItem
                                    room={room}
                                    mEvent={mEvent}
                                    onClose={closeMenu}
                                  />
                                )}
                                {mEvent.getSender() !== mx.getUserId() && (
                                  <MessageReportItem
                                    room={room}
                                    mEvent={mEvent}
                                    onClose={closeMenu}
                                  />
                                )}
                              </Box>
                            </>
                          )}
                        </Menu>
                      </FocusTrap>
                    }
                  >
                    <IconButton
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                      onClick={handleOpenMenu}
                      aria-label="More options"
                      aria-expanded={!!menuAnchor}
                      aria-haspopup="menu"
                    >
                      <Icon src={Icons.VerticalDots} size="100" />
                    </IconButton>
                  </PopOut>
                </Box>
              </Menu>
            </div>
          )}
          {messageLayout === MessageLayout.Compact && (
            <CompactLayout before={headerJSX} onContextMenu={handleContextMenu}>
              {msgContentJSX}
            </CompactLayout>
          )}
          {messageLayout === MessageLayout.Bubble && (
            <BubbleLayout before={avatarJSX} header={headerJSX} onContextMenu={handleContextMenu}>
              {msgContentJSX}
            </BubbleLayout>
          )}
          {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
            <ModernLayout before={avatarJSX} onContextMenu={handleContextMenu}>
              {headerJSX}
              {msgContentJSX}
            </ModernLayout>
          )}
          {forwardOpen && (
            <ForwardMessageDialog mEvent={mEvent} onClose={() => setForwardOpen(false)} />
          )}
        </MessageBase>
      );
    },
  ),
);

export type EventProps = {
  room: Room;
  mEvent: MatrixEvent;
  highlight: boolean;
  canDelete?: boolean;
  messageSpacing: MessageSpacing;
  hideReadReceipts?: boolean;
  showDeveloperTools?: boolean;
};
export const Event = React.memo(
  as<'div', EventProps>(
    (
      {
        className,
        room,
        mEvent,
        highlight,
        canDelete,
        messageSpacing,
        hideReadReceipts,
        showDeveloperTools,
        children,
        ...props
      },
      ref,
    ) => {
      const [hover, setHover] = useState(false);
      const mx = useMatrixClient();
      const { hoverProps } = useHover({ onHoverChange: setHover });
      const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
      const [menuAnchor, setMenuAnchor] = useState<RectCords>();
      const stateEvent = typeof mEvent.getStateKey() === 'string';
      const handleContextMenu: MouseEventHandler<HTMLDivElement> = (evt) => {
        if (evt.altKey || !window.getSelection()?.isCollapsed) return;
        const tag = (evt.target as any).tagName;
        if (typeof tag === 'string' && tag.toLowerCase() === 'a') return;
        evt.preventDefault();
        setMenuAnchor({
          x: evt.clientX,
          y: evt.clientY,
          width: 0,
          height: 0,
        });
      };

      const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
        const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
        setMenuAnchor(target.getBoundingClientRect());
      };

      const closeMenu = () => {
        setMenuAnchor(undefined);
      };

      return (
        <MessageBase
          className={classNames(css.MessageBase, className)}
          tabIndex={0}
          space={messageSpacing}
          autoCollapse
          highlight={highlight}
          selected={!!menuAnchor}
          {...props}
          {...hoverProps}
          {...focusWithinProps}
          ref={ref}
        >
          {(hover || !!menuAnchor) && (
            <div className={css.MessageOptionsBase}>
              <Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
                <Box gap="100">
                  <PopOut
                    anchor={menuAnchor}
                    position="Bottom"
                    align={menuAnchor?.width === 0 ? 'Start' : 'End'}
                    offset={menuAnchor?.width === 0 ? 0 : undefined}
                    content={
                      <FocusTrap
                        focusTrapOptions={{
                          initialFocus: false,
                          onDeactivate: () => setMenuAnchor(undefined),
                          clickOutsideDeactivates: true,
                          isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
                          isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
                          escapeDeactivates: stopPropagation,
                        }}
                      >
                        <Menu {...props} ref={ref}>
                          <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
                            {!hideReadReceipts && (
                              <MessageReadReceiptItem
                                room={room}
                                eventId={mEvent.getId() ?? ''}
                                onClose={closeMenu}
                              />
                            )}
                            {showDeveloperTools && (
                              <MessageSourceCodeItem
                                room={room}
                                mEvent={mEvent}
                                onClose={closeMenu}
                              />
                            )}
                            <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
                          </Box>
                          {((!mEvent.isRedacted() && canDelete && !stateEvent) ||
                            (mEvent.getSender() !== mx.getUserId() && !stateEvent)) && (
                            <>
                              <Line size="300" />
                              <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
                                {!mEvent.isRedacted() && canDelete && (
                                  <MessageDeleteItem
                                    room={room}
                                    mEvent={mEvent}
                                    onClose={closeMenu}
                                  />
                                )}
                                {mEvent.getSender() !== mx.getUserId() && (
                                  <MessageReportItem
                                    room={room}
                                    mEvent={mEvent}
                                    onClose={closeMenu}
                                  />
                                )}
                              </Box>
                            </>
                          )}
                        </Menu>
                      </FocusTrap>
                    }
                  >
                    <IconButton
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                      onClick={handleOpenMenu}
                      aria-label="More options"
                      aria-expanded={!!menuAnchor}
                      aria-haspopup="menu"
                    >
                      <Icon src={Icons.VerticalDots} size="100" />
                    </IconButton>
                  </PopOut>
                </Box>
              </Menu>
            </div>
          )}
          <div onContextMenu={handleContextMenu}>{children}</div>
        </MessageBase>
      );
    },
  ),
);
