import React, {
  KeyboardEventHandler,
  RefObject,
  forwardRef,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react';
import { useAtom, useAtomValue } from 'jotai';
import { isKeyHotkey } from 'is-hotkey';
import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk';
import { Transforms, Editor } from 'slate';
import { Box, Dialog, IconButton, Line, Overlay, OverlayBackdrop, OverlayCenter, PopOut, Scroll, Text, color, config, toRem } from 'folds';

import { Icon, Icons } from '../../components/icons';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import {
  CustomEditor,
  Toolbar,
  toMatrixCustomHTML,
  toPlainText,
  AUTOCOMPLETE_PREFIXES,
  AutocompletePrefix,
  AutocompleteQuery,
  getAutocompleteQuery,
  getPrevWorldRange,
  resetEditor,
  RoomMentionAutocomplete,
  UserMentionAutocomplete,
  EmoticonAutocomplete,
  createEmoticonElement,
  moveCursor,
  safeFocusEditor,
  resetEditorHistory,
  customHtmlEqualsPlainText,
  trimCustomHtml,
  isEmptyEditor,
  getBeginCommand,
  trimCommand,
  getMentions,
  setCommandValidator,
} from '../../components/editor';
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
import { UseStateProvider } from '../../components/UseStateProvider';
import {
  TUploadContent,
  getImageInfo,
  getMxIdLocalPart,
  mxcUrlToHttp,
} from '../../utils/matrix';
import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater';
import { useFilePicker } from '../../hooks/useFilePicker';
import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
import { useFileDropZone } from '../../hooks/useFileDrop';
import { useRoomUploadFiles } from '../../hooks/useRoomUploadFiles';
import {
  TUploadItem,
  TUploadMetadata,
  roomIdToMsgDraftAtomFamily,
  roomIdToReplyDraftAtomFamily,
  roomIdToUploadItemsAtomFamily,
  roomUploadAtomFamily,
} from '../../state/room/roomInputDrafts';
import { UploadCardRenderer } from '../../components/upload-card';
import {
  UploadBoard,
  UploadBoardContent,
  UploadBoardHeader,
  UploadBoardImperativeHandlers,
} from '../../components/upload-board';
import {
  Upload,
  UploadStatus,
  UploadSuccess,
  createUploadFamilyObserverAtom,
} from '../../state/upload';
import { getImageUrlBlob, loadImageElement } from '../../utils/dom';
import { fulfilledPromiseSettledResult } from '../../utils/common';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import {
  getAudioMsgContent,
  getFileMsgContent,
  getImageMsgContent,
  getVideoMsgContent,
} from './msgContent';
import { getMemberDisplayName, getMentionContent, trimReplyFromBody } from '../../utils/room';
import { CommandAutocomplete } from './CommandAutocomplete';
import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useCommands';
import { pluginRegistry } from '../settings/plugins/PluginAPI';
import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot';
import { mobileOrTablet } from '../../utils/user-agent';
import { ReplyLayout, ThreadIndicator } from '../../components/message';
import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
import colorMXID from '../../../util/colorMXID';
import { useIsDirectRoom } from '../../hooks/useRoom';
import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { isStationeryTheme, useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { useComposingCheck } from '../../hooks/useComposingCheck';
import { useOtherUserColor } from '../../hooks/useUserColor';
import { getMemberAvatarMxc } from '../../utils/room';
import { convertEmoticons, convertEmoticonsInHtml } from '../../utils/emoticonConverter';
import * as css from './RoomInput.css';

/**
 * Creates a UUID used to group image events into one carousel.
 */
const createCarouselUuid = (): string => {
  if (typeof globalThis.crypto?.randomUUID === 'function') {
    return globalThis.crypto.randomUUID();
  }

  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
};

interface RoomInputProps {
  editor: Editor;
  fileDropContainerRef: RefObject<HTMLElement>;
  roomId: string;
  room: Room;
  /** When provided, all messages are sent as replies in this thread. */
  threadRootId?: string;
  /** Thread parent for nested replies (no reply preview). */
  threadReplyToEventId?: string;
  /** Hide the "replying to" banner (forum thread composers). */
  hideReplyPreview?: boolean;
  /** Hide the thread badge in the reply preview (forum thread composers). */
  hideReplyThreadIndicator?: boolean;
  /** Optional highlight style for the reply preview (e.g. left border). */
  replyPreviewHighlightClassName?: string;
  onMessageSent?: () => void;
}
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
  (
    {
      editor,
      fileDropContainerRef,
      roomId,
      room,
      threadRootId,
      threadReplyToEventId,
      hideReplyPreview,
      hideReplyThreadIndicator,
      replyPreviewHighlightClassName,
      onMessageSent,
    },
    ref
  ) => {
    const mx = useMatrixClient();
    const useAuthentication = useMediaAuthentication();
    const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
    const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
    const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
    const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
    const [autoConvertEmoticons] = useSetting(settingsAtom, 'autoConvertEmoticons');
    const direct = useIsDirectRoom();
    const commands = useCommands(mx, room);
    const emojiBtnRef = useRef<HTMLButtonElement>(null);
    const roomToParents = useAtomValue(roomToParentsAtom);
    const powerLevels = usePowerLevelsContext();
    const creators = useRoomCreators(room);

    const [msgDraft, setMsgDraft] = useAtom(roomIdToMsgDraftAtomFamily(roomId));
    const [replyDraft, setReplyDraft] = useAtom(roomIdToReplyDraftAtomFamily(roomId));
    const replyUserID = replyDraft?.userId;
    const replyAvatarMxc = replyUserID ? getMemberAvatarMxc(room, replyUserID) : undefined;

    const powerLevelTags = usePowerLevelTags(room, powerLevels);
    const creatorsTag = useRoomCreatorsTag();
    const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
    const theme = useTheme();
    const accessibleTagColors = useAccessiblePowerTagColors(
      theme.kind,
      creatorsTag,
      powerLevelTags
    );

    // Get custom user color from avatar metadata
    const replyCustomColor = useOtherUserColor(replyUserID ?? '', replyAvatarMxc);

    const replyPowerTag = replyUserID ? getMemberPowerTag(replyUserID) : undefined;
    const replyPowerColor = replyPowerTag?.color
      ? accessibleTagColors.get(replyPowerTag.color)
      : undefined;
    // Priority: custom user color > legacy/direct colorMXID > tag color
    const replyUsernameColor =
      replyCustomColor ?? (legacyUsernameColor || direct ? colorMXID(replyUserID ?? '') : replyPowerColor);

    const [uploadBoard, setUploadBoard] = useState(true);
    const [selectedFiles, setSelectedFiles] = useAtom(roomIdToUploadItemsAtomFamily(roomId));
    const uploadFamilyObserverAtom = createUploadFamilyObserverAtom(
      roomUploadAtomFamily,
      selectedFiles.map((f) => f.file)
    );
    const uploadBoardHandlers = useRef<UploadBoardImperativeHandlers>();
    const editorRef = useRef<HTMLDivElement>(null);

    const imagePackRooms: Room[] = useImagePackRooms(roomId, roomToParents);

    const [toolbar, setToolbar] = useSetting(settingsAtom, 'editorToolbar');
    const [autocompleteQuery, setAutocompleteQuery] =
      useState<AutocompleteQuery<AutocompletePrefix>>();
    const [commandHint, setCommandHint] = useState<{
      command: string;
      description: string;
      args?: string[];
    }>();

    const sendTypingStatus = useTypingStatusUpdater(mx, roomId);

    useEffect(() => {
      setCommandValidator((commandName: string) => {
        const isBuiltInCommand = commands[commandName as Command];
        const isPluginCommand = pluginRegistry.getCommands().some(cmd => cmd.name === commandName);
        return !!(isBuiltInCommand || isPluginCommand);
      });
    }, [commands]);

    const { handleFiles: enqueueFiles } = useRoomUploadFiles(room);
    const handleFiles = useCallback(
      async (files: File[]) => {
        setUploadBoard(true);
        await enqueueFiles(files);
      },
      [enqueueFiles]
    );
    const pickFile = useFilePicker(handleFiles, true);
    const handlePaste = useFilePasteHandler(handleFiles);
    const dropZoneVisible = useFileDropZone(fileDropContainerRef, handleFiles);

    const isComposing = useComposingCheck();

    useEffect(() => {
      Transforms.insertFragment(editor, msgDraft);
    }, [editor, msgDraft]);

    useEffect(
      () => () => {
        if (!isEmptyEditor(editor)) {
          const parsedDraft = JSON.parse(JSON.stringify(editor.children));
          setMsgDraft(parsedDraft);
        } else {
          setMsgDraft([]);
        }
        resetEditor(editor);
        resetEditorHistory(editor);
      },
      [roomId, editor, setMsgDraft]
    );

    const handleFileMetadata = useCallback(
      (fileItem: TUploadItem, metadata: TUploadMetadata) => {
        setSelectedFiles({
          type: 'REPLACE',
          item: fileItem,
          replacement: { ...fileItem, metadata },
        });
      },
      [setSelectedFiles]
    );

    const handleRemoveUpload = useCallback(
      (upload: TUploadContent | TUploadContent[]) => {
        const uploads = Array.isArray(upload) ? upload : [upload];
        setSelectedFiles({
          type: 'DELETE',
          item: selectedFiles.filter((f) => uploads.find((u) => u === f.file)),
        });
        uploads.forEach((u) => roomUploadAtomFamily.remove(u));
      },
      [setSelectedFiles, selectedFiles]
    );

    const handleCancelUpload = (uploads: Upload[]) => {
      uploads.forEach((upload) => {
        if (upload.status === UploadStatus.Loading) {
          mx.cancelUpload(upload.promise);
        }
      });
      handleRemoveUpload(uploads.map((upload) => upload.file));
    };

    const handleSendUpload = async (uploads: UploadSuccess[]) => {
      const imageUploads = uploads.filter((upload) => {
        const fileItem = selectedFiles.find((f) => f.file === upload.file);
        return !!fileItem && fileItem.file.type.startsWith('image');
      });
      const carouselUuid = imageUploads.length > 1 ? createCarouselUuid() : undefined;
      const imageUploadIndexByFile = new Map(
        imageUploads.map((upload, index) => [upload.file, index] as const)
      );

      const contentsPromises = uploads.map(async (upload) => {
        const fileItem = selectedFiles.find((f) => f.file === upload.file);
        if (!fileItem) throw new Error('Broken upload');

        if (fileItem.file.type.startsWith('image')) {
          const imageIndex = imageUploadIndexByFile.get(upload.file);

          return getImageMsgContent(
            mx,
            fileItem,
            upload.mxc,
            carouselUuid !== undefined && imageIndex !== undefined
              ? {
                  uuid: carouselUuid,
                  index: imageIndex,
                  total: imageUploads.length,
                }
              : undefined
          );
        }
        if (fileItem.file.type.startsWith('video')) {
          return getVideoMsgContent(mx, fileItem, upload.mxc);
        }
        if (fileItem.file.type.startsWith('audio')) {
          return getAudioMsgContent(fileItem, upload.mxc);
        }
        return getFileMsgContent(fileItem, upload.mxc);
      });
      handleCancelUpload(uploads);
      const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
      contents.forEach((content) => mx.sendMessage(roomId, content as any));
    };

    const submit = useCallback(async () => {
      uploadBoardHandlers.current?.handleSend();

      let commandName = getBeginCommand(editor);
      let plainText = toPlainText(editor.children, isMarkdown).trim();
      let customHtml = trimCustomHtml(
        toMatrixCustomHTML(editor.children, {
          allowTextFormatting: true,
          allowBlockMarkdown: isMarkdown,
          allowInlineMarkdown: isMarkdown,
        })
      );
      let msgType = MsgType.Text;

      // If no CommandElement, check if text starts with a slash command
      if (!commandName && plainText.startsWith('/')) {
        const match = plainText.match(/^\/(\w+)(\s|$)/);
        if (match) {
          const potentialCommand = match[1];
          // Check if it's a valid command (built-in or plugin)
          const isBuiltInCommand = commands[potentialCommand as Command];
          const isPluginCommand = pluginRegistry.getCommands().some(cmd => cmd.name === potentialCommand);
          
          if (isBuiltInCommand || isPluginCommand) {
            commandName = potentialCommand;
          }
        }
      }

      if (commandName) {
        plainText = trimCommand(commandName, plainText);
        customHtml = trimCommand(commandName, customHtml);
      }
      if (commandName === Command.Me) {
        msgType = MsgType.Emote;
      } else if (commandName === Command.Notice) {
        msgType = MsgType.Notice;
      } else if (commandName === Command.Shrug) {
        plainText = `${SHRUG} ${plainText}`;
        customHtml = `${SHRUG} ${customHtml}`;
      } else if (commandName === Command.TableFlip) {
        plainText = `${TABLEFLIP} ${plainText}`;
        customHtml = `${TABLEFLIP} ${customHtml}`;
      } else if (commandName === Command.UnFlip) {
        plainText = `${UNFLIP} ${plainText}`;
        customHtml = `${UNFLIP} ${customHtml}`;
      } else if (commandName) {
        // Check built-in commands first
        const commandContent = commands[commandName as Command];
        if (commandContent) {
          commandContent.exe(plainText);
          resetEditor(editor);
          resetEditorHistory(editor);
          setCommandHint(undefined);
          sendTypingStatus(false);
          return;
        }

        // Check plugin commands
        try {
          const result = await pluginRegistry.executeCommand(commandName, plainText, roomId);
          if (result) {
            // If command returns text, send it as a message
            const content: IContent = {
              msgtype: MsgType.Text,
              body: result,
            };
            mx.sendMessage(roomId, content as any);
          }
        } catch (err) {
          console.error(`[RoomInput] Plugin command /${commandName} failed:`, err);
          // Show error to user (could integrate with notification system)
        }
        resetEditor(editor);
        resetEditorHistory(editor);
        setCommandHint(undefined);
        sendTypingStatus(false);
        return;
      }

      let body = plainText.trim();
      let formattedBody = customHtml.trim().replace(/(<br\s*\/?>\s*)+$/gi, '');

      if (body === '') return;

      // Apply emoticon conversion if enabled
      if (autoConvertEmoticons) {
        body = convertEmoticons(body);
        formattedBody = convertEmoticonsInHtml(formattedBody);
      }

      const mentionData = getMentions(mx, roomId, editor);

      const content: IContent = {
        msgtype: msgType,
        body,
      };

      if (replyDraft && replyDraft.userId !== mx.getUserId()) {
        mentionData.users.add(replyDraft.userId);
      }

      const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room);
      content['m.mentions'] = mMentions;

      if (replyDraft || !customHtmlEqualsPlainText(formattedBody, body)) {
        content.format = 'org.matrix.custom.html';
        content.formatted_body = formattedBody;
      }
      if (replyDraft) {
        content['m.relates_to'] = {
          'm.in_reply_to': {
            event_id: replyDraft.eventId,
          },
        };
        if (replyDraft.relation?.rel_type === RelationType.Thread) {
          content['m.relates_to'].event_id = replyDraft.relation.event_id;
          content['m.relates_to'].rel_type = RelationType.Thread;
          content['m.relates_to'].is_falling_back = false;
        }
      } else if (threadRootId) {
        const inReplyToEventId = threadReplyToEventId ?? threadRootId;
        content['m.relates_to'] = {
          rel_type: RelationType.Thread,
          event_id: threadRootId,
          is_falling_back: inReplyToEventId === threadRootId,
          'm.in_reply_to': { event_id: inReplyToEventId },
        };
      }
      
      // Process message through plugin interceptors
      try {
        const messageContext = {
          content: content.body,
          roomId,
          eventType: 'm.room.message',
          formatted: content.formatted_body,
          metadata: {},
        };
        const processedContext = await pluginRegistry.processBeforeSend(messageContext);
        
        // Update content with intercepted values
        content.body = processedContext.content;
        if (processedContext.formatted) {
          content.formatted_body = processedContext.formatted;
        }
      } catch (err) {
        console.error('[RoomInput] Plugin interceptor error:', err);
      }
      
      mx.sendMessage(roomId, content as any);
      resetEditor(editor);
      resetEditorHistory(editor);
      setReplyDraft(undefined);
      setCommandHint(undefined);
      sendTypingStatus(false);
      onMessageSent?.();
    }, [mx, roomId, threadRootId, threadReplyToEventId, editor, replyDraft, sendTypingStatus, setReplyDraft, isMarkdown, commands, autoConvertEmoticons, onMessageSent]);

    const handleKeyDown: KeyboardEventHandler = useCallback(
      (evt) => {
        if (
          (isKeyHotkey('mod+enter', evt) || (!enterForNewline && isKeyHotkey('enter', evt))) &&
          !isComposing(evt)
        ) {
          evt.preventDefault();
          submit();
        }
        if (isKeyHotkey('escape', evt)) {
          evt.preventDefault();
          if (autocompleteQuery) {
            setAutocompleteQuery(undefined);
            return;
          }
          if (commandHint) {
            setCommandHint(undefined);
            return;
          }
          setReplyDraft(undefined);
        }
      },
      [submit, setReplyDraft, enterForNewline, autocompleteQuery, isComposing, commandHint]
    );

    const handleKeyUp: KeyboardEventHandler = useCallback(
      (evt) => {
        if (isKeyHotkey('escape', evt)) {
          evt.preventDefault();
          return;
        }

        if (!hideActivity) {
          sendTypingStatus(!isEmptyEditor(editor));
        }

        const prevWordRange = getPrevWorldRange(editor);
        const query = prevWordRange
          ? getAutocompleteQuery<AutocompletePrefix>(editor, prevWordRange, AUTOCOMPLETE_PREFIXES)
          : undefined;
        setAutocompleteQuery(query);

        // Check if user is typing a command and show hint
        const plainText = toPlainText(editor.children, false).trim();
        const commandMatch = plainText.match(/^\/(\w+)(\s|$)/);
        
        if (commandMatch) {
          const commandName = commandMatch[1];
          const builtInCmd = commands[commandName as Command];
          const pluginCmd = pluginRegistry.getCommands().find(cmd => cmd.name === commandName);
          
          if (builtInCmd) {
            setCommandHint({
              command: commandName,
              description: builtInCmd.description,
            });
          } else if (pluginCmd) {
            setCommandHint({
              command: commandName,
              description: pluginCmd.command.description || '',
              args: pluginCmd.command.args,
            });
          } else {
            setCommandHint(undefined);
          }
        } else {
          setCommandHint(undefined);
        }
      },
      [editor, sendTypingStatus, hideActivity, commands]
    );

    const handleCloseAutocomplete = useCallback(() => {
      setAutocompleteQuery(undefined);
      safeFocusEditor(editor);
    }, [editor]);

    const handleEmoticonSelect = (key: string, shortcode: string) => {
      editor.insertNode(createEmoticonElement(key, shortcode));
      moveCursor(editor);
    };

    const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => {
      const stickerUrl = mxcUrlToHttp(mx, mxc, useAuthentication);
      if (!stickerUrl) return;

      const info = await getImageInfo(
        await loadImageElement(stickerUrl),
        await getImageUrlBlob(stickerUrl)
      );

      mx.sendEvent(roomId, EventType.Sticker, {
        body: label,
        url: mxc,
        info,
      });
    };

    return (
      <div ref={ref} className={css.RoomInputWrap}>
        {selectedFiles.length > 0 && (
          <UploadBoard
            header={
              <UploadBoardHeader
                open={uploadBoard}
                onToggle={() => setUploadBoard(!uploadBoard)}
                uploadFamilyObserverAtom={uploadFamilyObserverAtom}
                onSend={handleSendUpload}
                imperativeHandlerRef={uploadBoardHandlers}
                onCancel={handleCancelUpload}
              />
            }
          >
            {uploadBoard && (
              <Scroll size="300" hideTrack visibility="Hover">
                <UploadBoardContent>
                  {Array.from(selectedFiles)
                    .reverse()
                    .map((fileItem, index) => (
                      <UploadCardRenderer
                        // eslint-disable-next-line react/no-array-index-key
                        key={index}
                        isEncrypted={!!fileItem.encInfo}
                        fileItem={fileItem}
                        setMetadata={handleFileMetadata}
                        onRemove={handleRemoveUpload}
                      />
                    ))}
                </UploadBoardContent>
              </Scroll>
            )}
          </UploadBoard>
        )}
        <Overlay
          open={dropZoneVisible}
          backdrop={<OverlayBackdrop />}
          style={{ pointerEvents: 'none' }}
        >
          <OverlayCenter>
            <Dialog variant="Primary">
              <Box
                direction="Column"
                justifyContent="Center"
                alignItems="Center"
                gap="500"
                style={{ padding: toRem(60) }}
              >
                <Icon size="600" src={Icons.File} />
                <Text size="H4" align="Center">
                  {`Drop Files in "${room?.name || 'Room'}"`}
                </Text>
                <Text align="Center">Drag and drop files here or click for selection dialog</Text>
              </Box>
            </Dialog>
          </OverlayCenter>
        </Overlay>
        {autocompleteQuery?.prefix === AutocompletePrefix.RoomMention && (
          <RoomMentionAutocomplete
            roomId={roomId}
            editor={editor}
            query={autocompleteQuery}
            requestClose={handleCloseAutocomplete}
          />
        )}
        {autocompleteQuery?.prefix === AutocompletePrefix.UserMention && (
          <UserMentionAutocomplete
            room={room}
            editor={editor}
            query={autocompleteQuery}
            requestClose={handleCloseAutocomplete}
          />
        )}
        {autocompleteQuery?.prefix === AutocompletePrefix.Emoticon && (
          <EmoticonAutocomplete
            imagePackRooms={imagePackRooms}
            editor={editor}
            query={autocompleteQuery}
            requestClose={handleCloseAutocomplete}
          />
        )}
        {autocompleteQuery?.prefix === AutocompletePrefix.Command && !commandHint && (
          <CommandAutocomplete
            room={room}
            editor={editor}
            query={autocompleteQuery}
            requestClose={handleCloseAutocomplete}
          />
        )}
        <CustomEditor
          ref={editorRef}
          editableName="RoomInput"
          editor={editor}
          placeholder="Send a message..."
          onKeyDown={handleKeyDown}
          onKeyUp={handleKeyUp}
          onPaste={handlePaste}
          top={
            <>
              {commandHint && (
                <Box
                  style={{
                    padding: `${config.space.S200} ${config.space.S300}`,
                    backgroundColor: color.SurfaceVariant.Container,
                    border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
                    borderBottom: 'none',
                    borderRadius: isStationeryTheme(theme) ? 0 : undefined,
                    borderTopLeftRadius: isStationeryTheme(theme) ? undefined : config.radii.R400,
                    borderTopRightRadius: isStationeryTheme(theme) ? undefined : config.radii.R400,
                    marginBottom: config.space.S100,
                  }}
                  direction="Column"
                  gap="100"
                >
                  <Text size="T200" priority="400">
                    <b>/{commandHint.command}</b>
                    {commandHint.args && commandHint.args.length > 0 && (
                      <span style={{ opacity: 0.6 }}> {commandHint.args.map(arg => `[${arg}]`).join(' ')}</span>
                    )}
                  </Text>
                  {commandHint.description && (
                    <Text size="T200" priority="300" style={{ opacity: 0.7 }}>
                      {commandHint.description}
                    </Text>
                  )}
                </Box>
              )}
              {replyDraft && !hideReplyPreview && (
                <div>
                  <Box
                    alignItems="Center"
                    gap="300"
                    style={{ padding: `${config.space.S200} ${config.space.S300} 0` }}
                  >
                    <IconButton
                      onClick={() => setReplyDraft(undefined)}
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                    >
                      <Icon src={Icons.Cross} size="50" />
                    </IconButton>
                    <Box
                      direction="Row"
                      gap="200"
                      alignItems="Center"
                      grow="Yes"
                      className={replyPreviewHighlightClassName}
                    >
                      {!hideReplyThreadIndicator &&
                        replyDraft.relation?.rel_type === RelationType.Thread && <ThreadIndicator />}
                      <ReplyLayout
                        userColor={replyUsernameColor}
                        username={
                          <Text size="T300" truncate>
                            <b>
                              {getMemberDisplayName(room, replyDraft.userId) ??
                                getMxIdLocalPart(replyDraft.userId) ??
                                replyDraft.userId}
                            </b>
                          </Text>
                        }
                      >
                        <Text size="T300" truncate>
                          {trimReplyFromBody(replyDraft.body)}
                        </Text>
                      </ReplyLayout>
                    </Box>
                  </Box>
                </div>
              )}
            </>
          }
          before={
            <>
              <IconButton
                onClick={() => pickFile('*')}
                variant="SurfaceVariant"
                size="300"
                radii="300"
              >
                <Icon src={Icons.PlusCircle} size="200" />
              </IconButton>
              <PluginButtonSlot location="composer-actions" />
            </>
          }
          after={
            <>
              <IconButton
                variant="SurfaceVariant"
                size="300"
                radii="300"
                onClick={() => setToolbar(!toolbar)}
              >
                <Icon src={toolbar ? Icons.AlphabetUnderline : Icons.Alphabet} size="200" />
              </IconButton>
              <UseStateProvider initial={undefined}>
                {(emojiBoardTab: EmojiBoardTab | undefined, setEmojiBoardTab) => (
                  <PopOut
                    offset={16}
                    alignOffset={-44}
                    position="Top"
                    align="End"
                    anchor={
                      emojiBoardTab === undefined
                        ? undefined
                        : emojiBtnRef.current?.getBoundingClientRect() ?? undefined
                    }
                    content={
                      <EmojiBoard
                        tab={emojiBoardTab}
                        onTabChange={setEmojiBoardTab}
                        imagePackRooms={imagePackRooms}
                        returnFocusOnDeactivate={false}
                        onEmojiSelect={handleEmoticonSelect}
                        onCustomEmojiSelect={handleEmoticonSelect}
                        onStickerSelect={handleStickerSelect}
                        requestClose={() => {
                          setEmojiBoardTab((t) => {
                            if (t) {
                              if (!mobileOrTablet()) safeFocusEditor(editor);
                              return undefined;
                            }
                            return t;
                          });
                        }}
                      />
                    }
                  >
                    <IconButton
                      ref={emojiBtnRef}
                      aria-pressed={emojiBoardTab === EmojiBoardTab.Emoji}
                      onClick={() => setEmojiBoardTab(EmojiBoardTab.Emoji)}
                      variant="SurfaceVariant"
                      size="300"
                      radii="300"
                    >
                      <Icon
                        src={Icons.Smile}
                        size="200"
                        filled={emojiBoardTab === EmojiBoardTab.Emoji}
                      />
                    </IconButton>
                  </PopOut>
                )}
              </UseStateProvider>
              <PluginButtonSlot location="text-composer-toolbar" />
              <IconButton onClick={submit} variant="SurfaceVariant" size="300" radii="300">
                <Icon src={Icons.Send} size="200" />
              </IconButton>
            </>
          }
          bottom={
            toolbar && (
              <div>
                <Line variant="SurfaceVariant" size="300" />
                <Toolbar />
              </div>
            )
          }
        />
      </div>
    );
  }
);
