import React, {
  MouseEventHandler,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  MatrixEvent,
  RelationType,
  Room,
  RoomEvent,
  ThreadEvent,
} from 'matrix-js-sdk';
import { useAtomValue, useSetAtom } from 'jotai';
import { Badge, Box, IconButton, Line, Scroll, Text, Tooltip, TooltipProvider, config } from 'folds';
import { Icon, Icons } from '../../components/icons';
import { HTMLReactParserOptions } from 'html-react-parser';
import { Opts as LinkifyOpts } from 'linkifyjs';
import { ReactEditor } from 'slate-react';
import { isKeyHotkey } from 'is-hotkey';

import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useKeyDown } from '../../hooks/useKeyDown';
import { editableActiveElement } from '../../utils/dom';
import {
  useEditor,
  createMentionElement,
  moveCursor,
} from '../../components/editor';
import { useSetting } from '../../state/hooks/settings';
import { MessageLayout, settingsAtom } from '../../state/settings';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
import { useSpaceOptionally } from '../../hooks/useSpace';
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import {
  useAccessiblePowerTagColors,
  useGetMemberPowerTag,
} from '../../hooks/useMemberPowerTag';
import {
  combineEmbedFilters,
  useRoomEmbedFilters,
  useRoomWideEmbedFilters,
} from '../../hooks/useRoomEmbedFilters';
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
import { useIsDirectRoom } from '../../hooks/useRoom';
import { roomToParentsAtom } from '../../state/room/roomToParents';
import { activeThreadIdAtomFamily } from '../../state/activeThread';
import { roomIdToReplyDraftAtomFamily } from '../../state/room/roomInputDrafts';
import { RoomInput } from './RoomInput';
import { RoomViewTyping } from './RoomViewTyping';
import { RoomViewFollowing } from './RoomViewFollowing';
import * as roomViewCss from './RoomViewFollowing.css';
import { Message, Reactions, EncryptedContent } from './message';
import { Reply } from '../../components/message';
import { RenderMessageContent } from '../../components/RenderMessageContent';
import { EmojiConfettiOverlay } from './emoji-confetti/EmojiConfettiOverlay';
import { useJumboEmojiConfetti } from './emoji-confetti/useJumboEmojiConfetti';
import {
  LINKIFY_OPTS,
  factoryRenderLinkifyWithMention,
  getReactCustomHtmlParser,
  makeMentionCustomProps,
  renderMatrixMention,
} from '../../plugins/react-custom-html-parser';
import {
  getEditedEvent,
  getEventReactions,
  getMemberDisplayName,
  getReactionContent,
  reactionOrEditEvent,
} from '../../utils/room';
import {
  eventWithShortcode,
  factoryEventSentBy,
  getMxIdLocalPart,
} from '../../utils/matrix';
import { GetContentCallback, MessageEvent } from '../../../types/matrix/room';
import { inSameDay, minuteDifference, timeDayMonthYear, today, yesterday } from '../../utils/time';
import { autoScrollToBottom, scrollElementIntoView } from '../../utils/dom';

type ThreadViewProps = {
  room: Room;
  threadRootId: string;
};

const FN_KEYS_REGEX = /^F\d+$/;
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
  const { code } = evt;
  if (evt.metaKey || evt.altKey || evt.ctrlKey) {
    return false;
  }

  if (FN_KEYS_REGEX.test(code)) return false;

  if (
    code.startsWith('OS') ||
    code.startsWith('Meta') ||
    code.startsWith('Shift') ||
    code.startsWith('Alt') ||
    code.startsWith('Control') ||
    code.startsWith('Arrow') ||
    code.startsWith('Page') ||
    code.startsWith('End') ||
    code.startsWith('Home') ||
    code === 'Tab' ||
    code === 'Space' ||
    code === 'Enter' ||
    code === 'NumLock' ||
    code === 'ScrollLock'
  ) {
    return false;
  }

  return true;
};

/**
 * Renders a thread inline, replacing the main room timeline.
 * Structured as direct flex siblings of `Page` to match the RoomView layout contract.
 */
export function ThreadView({ room, threadRootId }: ThreadViewProps) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const setActiveThreadId = useSetAtom(activeThreadIdAtomFamily(room.roomId));
  const setReplyDraft = useSetAtom(roomIdToReplyDraftAtomFamily(room.roomId));

  // eslint-disable-next-line no-console
  console.log('[ThreadView] Component mount/render', {
    roomId: room.roomId,
    threadRootId,
    allThreadsInRoom: room.getThreads().map(t => ({
      id: t.id,
      rootEventId: t.rootEvent?.getId(),
      length: t.length,
      hasCurrentUserParticipated: t.hasCurrentUserParticipated,
    })),
  });

  const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
  const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
  const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
  const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
  const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
  const [encUrlPreview] = useSetting(settingsAtom, 'encUrlPreview');
  const [developerTools] = useSetting(settingsAtom, 'developerTools');
  const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
  const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
  const { bursts: emojiConfettiBursts, removeBurst: removeEmojiConfettiBurst, handleJumboEmojiClick } =
    useJumboEmojiConfetti(room);

  const showUrlPreview = room.hasEncryptionStateEvent() ? encUrlPreview : urlPreview;
  const direct = useIsDirectRoom();

  const powerLevels = usePowerLevelsContext();
  const creators = useRoomCreators(room);
  const creatorsTag = useRoomCreatorsTag();
  const powerLevelTags = usePowerLevelTags(room, powerLevels);
  const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
  const theme = useTheme();
  const accessiblePowerTagColors = useAccessiblePowerTagColors(
    theme.kind,
    creatorsTag,
    powerLevelTags
  );
  const permissions = useRoomPermissions(creators, powerLevels);
  const canRedact = permissions.action('redact', mx.getSafeUserId());
  const canSendReaction = permissions.event(MessageEvent.Reaction, mx.getSafeUserId());
  const canPinEvent = permissions.stateEvent('m.room.pinned_events', mx.getSafeUserId());

  const roomToParents = useAtomValue(roomToParentsAtom);
  const imagePackRooms = useImagePackRooms(room.roomId, roomToParents);
  const space = useSpaceOptionally();
  const openUserRoomProfile = useOpenUserRoomProfile();
  const ignoredUsersList = useIgnoredUsers();
  const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]);

  const [personalEmbedFilters] = useRoomEmbedFilters(room);
  const roomWideEmbedFilters = useRoomWideEmbedFilters(room);
  const combinedEmbedFilters = useMemo(
    () => combineEmbedFilters(personalEmbedFilters, roomWideEmbedFilters),
    [personalEmbedFilters, roomWideEmbedFilters]
  );

  const mentionClickHandler = useMentionClickHandler(room.roomId);
  const spoilerClickHandler = useSpoilerClickHandler();

  const linkifyOpts = useMemo<LinkifyOpts>(
    () => ({
      ...LINKIFY_OPTS,
      render: factoryRenderLinkifyWithMention((href) =>
        renderMatrixMention(mx, room.roomId, href, makeMentionCustomProps(mentionClickHandler))
      ),
    }),
    [mx, room, mentionClickHandler]
  );

  const htmlReactParserOptions = useMemo<HTMLReactParserOptions>(
    () =>
      getReactCustomHtmlParser(mx, room.roomId, {
        linkifyOpts,
        useAuthentication,
        handleSpoilerClick: spoilerClickHandler,
        handleMentionClick: mentionClickHandler,
      }),
    [mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
  );

  const thread = room.getThread(threadRootId);
  const roomTimelineSet = room.getUnfilteredTimelineSet();

  // If thread doesn't exist, try to find it by checking if threadRootId is a root event
  React.useEffect(() => {
    if (!thread) {
      // eslint-disable-next-line no-console
      console.log('[ThreadView] Thread not found, checking if event exists in room timeline');
      const rootEvent = room.findEventById(threadRootId);
      // eslint-disable-next-line no-console
      console.log('[ThreadView] Root event lookup', {
        threadRootId,
        rootEventFound: !!rootEvent,
        rootEventType: rootEvent?.getType(),
        rootEventThreadRootId: rootEvent?.threadRootId,
        allEventIdsInTimeline: roomTimelineSet.getLiveTimeline().getEvents().slice(0, 20).map(e => e.getId()),
      });
    }
  }, [thread, room, threadRootId, roomTimelineSet]);

  const getThreadEvents = useCallback((): MatrixEvent[] => {
    const t = room.getThread(threadRootId);
    if (!t) {
      // eslint-disable-next-line no-console
      console.log('[ThreadView] No thread found for ID:', threadRootId);
      // eslint-disable-next-line no-console
      console.log('[ThreadView] Attempting to find replies manually from room timeline');
      
      // Fallback: search the room timeline for events that are replies to this thread
      const allEvents = roomTimelineSet.getLiveTimeline().getEvents();
      const threadReplies = allEvents.filter(event => {
        const relations = event.getRelation();
        // Check if this event is a thread reply to our root event
        return relations?.rel_type === 'm.thread' && relations?.event_id === threadRootId;
      });
      
      // eslint-disable-next-line no-console
      console.log('[ThreadView] Manual search found', {
        totalEventsInTimeline: allEvents.length,
        repliesFound: threadReplies.length,
        replies: threadReplies.map(e => ({
          id: e.getId(),
          sender: e.getSender(),
          body: e.getContent()?.body?.substring(0, 50),
        })),
      });
      
      return threadReplies;
    }
    
    // thread.events contains only replies, not the root event
    const replies = [...t.events];
    const liveTimelineEvents = t.liveTimeline?.getEvents() || [];
    
    // eslint-disable-next-line no-console
    console.log('[ThreadView] getThreadEvents', {
      threadId: threadRootId,
      repliesCount: replies.length,
      threadLength: t.length,
      hasRootEvent: !!t.rootEvent,
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      initialEventsFetched: (t as any).initialEventsFetched,
      liveTimelineEventsCount: liveTimelineEvents.length,
      threadEventsMatch: replies.length === liveTimelineEvents.length,
      allRepliesFromThreadEvents: replies.map(e => ({ 
        id: e.getId(), 
        type: e.getType(), 
        sender: e.getSender(),
        content: e.getContent()?.body?.substring(0, 50),
      })),
      allEventsFromLiveTimeline: liveTimelineEvents.map(e => ({
        id: e.getId(),
        type: e.getType(),
        sender: e.getSender(),
        content: e.getContent()?.body?.substring(0, 50),
      })),
    });
    
    // Try using liveTimeline events if thread.events is empty but liveTimeline has events
    if (replies.length === 0 && liveTimelineEvents.length > 0) {
      // eslint-disable-next-line no-console
      console.log('[ThreadView] Using liveTimeline events instead of thread.events');
      return [...liveTimelineEvents];
    }
    
    return replies;
  }, [room, threadRootId, roomTimelineSet]);

  const [events, setEvents] = useState<MatrixEvent[]>(getThreadEvents);
  const [editId, setEditId] = useState<string | undefined>(undefined);
  const scrollRef = useRef<HTMLDivElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLDivElement>(null);
  const editor = useEditor();

  useKeyDown(
    window,
    useCallback(
      (evt) => {
        if (editableActiveElement()) return;
        const portalContainer = document.getElementById('portalContainer');
        if (portalContainer && portalContainer.children.length > 0) {
          return;
        }
        if (shouldFocusMessageField(evt)) {
          evt.preventDefault();
          ReactEditor.focus(editor);
          if (evt.key.length === 1) {
            editor.insertText(evt.key);
          }
        } else if (isKeyHotkey('mod+v', evt)) {
          ReactEditor.focus(editor);
        }
      },
      [editor]
    )
  );

  // Subscribe to thread and room timeline events.
  useEffect(() => {
    // eslint-disable-next-line no-console
    console.log('[ThreadView] Effect running', {
      threadId: threadRootId,
      threadExists: !!thread,
      threadLength: thread?.length,
      eventsCount: thread?.events?.length,
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      initialEventsFetched: (thread as any)?.initialEventsFetched,
    });

    const handleUpdate = () => {
      const newEvents = getThreadEvents();
      // eslint-disable-next-line no-console
      console.log('[ThreadView] handleUpdate - new events count:', newEvents.length);
      setEvents(newEvents);
    };

    // Immediately refresh in case events loaded between render and this effect.
    handleUpdate();

    if (thread) {
      thread.on(ThreadEvent.NewReply, handleUpdate);
      thread.on(ThreadEvent.Update, handleUpdate);
      // Fires when initial events are loaded after the SDK paginates the thread.
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (thread as any).on(RoomEvent.TimelineReset, handleUpdate);
      
      // Also listen to Timeline events which fire when events are added
      thread.on(RoomEvent.Timeline, handleUpdate);
    }

    const handleRoomTimeline = (mEvent: MatrixEvent, eventRoom: Room | undefined) => {
      if (eventRoom?.roomId !== room.roomId) return;
      // Check if this event is related to our thread (either it's a reply to it, or it IS the root event)
      const relations = mEvent.getRelation();
      const isThreadReply = relations?.rel_type === 'm.thread' && relations?.event_id === threadRootId;
      const isRootEvent = mEvent.getId() === threadRootId;
      
      if (isThreadReply || isRootEvent) {
        handleUpdate();
      }
    };

    room.on(RoomEvent.Timeline, handleRoomTimeline as never);
    room.on(RoomEvent.Redaction, handleRoomTimeline as never);

    return () => {
      if (thread) {
        thread.off(ThreadEvent.NewReply, handleUpdate);
        thread.off(ThreadEvent.Update, handleUpdate);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (thread as any).off(RoomEvent.TimelineReset, handleUpdate);
        thread.off(RoomEvent.Timeline, handleUpdate);
      }
      room.off(RoomEvent.Timeline, handleRoomTimeline as never);
      room.off(RoomEvent.Redaction, handleRoomTimeline as never);
    };
  }, [thread, room, threadRootId, getThreadEvents]);

  // Scroll to bottom on mount and when new events arrive.
  useLayoutEffect(() => {
    const el = scrollRef.current;
    if (el) autoScrollToBottom(el);
  }, [events.length]);

  const handleClose = useCallback(() => {
    setActiveThreadId(undefined);
    setReplyDraft(undefined);
  }, [setActiveThreadId, setReplyDraft]);

  const handleUserClick: MouseEventHandler<HTMLButtonElement> = useCallback(
    (evt) => {
      evt.preventDefault();
      evt.stopPropagation();
      const userId = evt.currentTarget.getAttribute('data-user-id');
      if (!userId) return;
      openUserRoomProfile(
        room.roomId,
        space?.roomId,
        userId,
        evt.currentTarget.getBoundingClientRect()
      );
    },
    [room, space, openUserRoomProfile]
  );

  const handleUsernameClick: MouseEventHandler<HTMLButtonElement> = useCallback(
    (evt) => {
      evt.preventDefault();
      const userId = evt.currentTarget.getAttribute('data-user-id');
      if (!userId) return;
      const name = getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId;
      editor.insertNode(
        createMentionElement(
          userId,
          name.startsWith('@') ? name : `@${name}`,
          userId === mx.getUserId()
        )
      );
      ReactEditor.focus(editor);
      moveCursor(editor);
    },
    [mx, room, editor]
  );

  const handleReplyClick: MouseEventHandler<HTMLButtonElement> = useCallback(
    (evt) => {
      const replyId = evt.currentTarget.getAttribute('data-event-id');
      if (!replyId) return;
      const replyEvt =
        thread?.findEventById(replyId) ?? room.findEventById(replyId);
      if (!replyEvt) return;
      const editedReply = getEditedEvent(replyId, replyEvt, roomTimelineSet);
      const content =
        editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent();
      const { body, formatted_body: formattedBody } = content;
      const senderId = replyEvt.getSender();
      if (senderId && typeof body === 'string') {
        setReplyDraft({
          userId: senderId,
          eventId: replyId,
          body,
          formattedBody,
          relation: { rel_type: RelationType.Thread, event_id: threadRootId },
        });
        setTimeout(() => ReactEditor.focus(editor), 100);
      }
    },
    [room, thread, roomTimelineSet, threadRootId, setReplyDraft, editor]
  );

  const handleOpenReply: MouseEventHandler = useCallback((evt) => {
    const targetId = evt.currentTarget.getAttribute('data-event-id');
    if (!targetId) return;
    const el = scrollRef.current?.querySelector(
      `[data-message-id="${targetId}"]`
    ) as HTMLElement | null;
    if (el) scrollElementIntoView(el, { behavior: 'smooth', block: 'center' });
  }, []);

  const handleReactionToggle = useCallback(
    (targetEventId: string, key: string, shortcode?: string) => {
      const relations = getEventReactions(roomTimelineSet, targetEventId);
      const allReactions = relations?.getSortedAnnotationsByKey() ?? [];
      const [, reactionsSet] = allReactions.find(([k]) => k === key) ?? [];
      const reactions = reactionsSet ? Array.from(reactionsSet) : [];
      const myReaction = reactions.find(factoryEventSentBy(mx.getSafeUserId()));

      if (myReaction && !!myReaction?.isRelation()) {
        const myReactionId = myReaction.getId();
        if (myReactionId) mx.redactEvent(room.roomId, myReactionId);
        return;
      }
      const rShortcode =
        shortcode ||
        (reactions.find(eventWithShortcode)?.getContent().shortcode as string | undefined);
      mx.sendEvent(
        room.roomId,
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        MessageEvent.Reaction as any,
        getReactionContent(targetEventId, key, rShortcode)
      );
    },
    [mx, room, roomTimelineSet]
  );

  /** Renders a single thread event into JSX. Returns null for non-message events. */
  const renderEvent = (mEvent: MatrixEvent, index: number, all: MatrixEvent[]) => {
    const mEventId = mEvent.getId();
    if (!mEventId) return null;
    const eventSender = mEvent.getSender();
    if (eventSender && ignoredUsersSet.has(eventSender)) return null;
    if (mEvent.isRedacted()) return null;
    if (reactionOrEditEvent(mEvent)) return null;

    const type = mEvent.getType();
    if (
      type !== MessageEvent.RoomMessage &&
      type !== MessageEvent.RoomMessageEncrypted
    ) {
      return null;
    }

    const prevEvent = index > 0 ? all[index - 1] : undefined;
    const dayDivider = prevEvent
      ? !inSameDay(prevEvent.getTs(), mEvent.getTs())
      : false;

    const collapse =
      !dayDivider &&
      prevEvent !== undefined &&
      prevEvent.getSender() === eventSender &&
      prevEvent.getType() === type &&
      minuteDifference(prevEvent.getTs(), mEvent.getTs()) < 2;

    const reactionRelations = getEventReactions(roomTimelineSet, mEventId);
    const hasReactions =
      !!reactionRelations?.getSortedAnnotationsByKey()?.length;

    const { replyEventId, threadRootId: evtThreadRootId } = mEvent;

    const editedEvent = getEditedEvent(mEventId, mEvent, roomTimelineSet);
    const getContent = (() =>
      editedEvent?.getContent()['m.new_content'] ??
      mEvent.getContent()) as GetContentCallback;

    const senderId = mEvent.getSender() ?? '';
    const senderDisplayName =
      getMemberDisplayName(room, senderId) ??
      getMxIdLocalPart(senderId) ??
      senderId;

    const dayDividerJSX = dayDivider ? (
      <Box
        key={`divider-${mEventId}`}
        gap="100"
        justifyContent="Center"
        alignItems="Center"
        style={{ padding: `${config.space.S200} 0` }}
      >
        <Line style={{ flexGrow: 1 }} variant="Surface" size="300" />
        <Badge as="span" size="500" variant="Secondary" fill="None" radii="300">
          <Text size="L400">
            {(() => {
              if (today(mEvent.getTs())) return 'Today';
              if (yesterday(mEvent.getTs())) return 'Yesterday';
              return timeDayMonthYear(mEvent.getTs());
            })()}
          </Text>
        </Badge>
        <Line style={{ flexGrow: 1 }} variant="Surface" size="300" />
      </Box>
    ) : null;

    const messageJSX = (
      <Message
        key={mEventId}
        data-message-id={mEventId}
        room={room}
        mEvent={mEvent}
        messageSpacing={messageSpacing}
        messageLayout={messageLayout}
        collapse={collapse}
        highlight={false}
        edit={editId === mEventId}
        canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
        canSendReaction={canSendReaction}
        canPinEvent={canPinEvent}
        imagePackRooms={imagePackRooms}
        relations={hasReactions ? reactionRelations : undefined}
        onUserClick={handleUserClick}
        onUsernameClick={handleUsernameClick}
        onReplyClick={handleReplyClick}
        onReactionToggle={handleReactionToggle}
        onEditId={setEditId}
        hideReadReceipts={hideActivity}
        showDeveloperTools={developerTools}
        memberPowerTag={getMemberPowerTag(senderId)}
        accessibleTagColors={accessiblePowerTagColors}
        legacyUsernameColor={legacyUsernameColor || direct}
        hour24Clock={hour24Clock}
        dateFormatString={dateFormatString}
        reply={
          replyEventId && replyEventId !== threadRootId ? (
            <Reply
              room={room}
              timelineSet={roomTimelineSet}
              replyEventId={replyEventId}
              threadRootId={evtThreadRootId}
              onClick={handleOpenReply}
              getMemberPowerTag={getMemberPowerTag}
              accessibleTagColors={accessiblePowerTagColors}
              legacyUsernameColor={legacyUsernameColor || direct}
            />
          ) : undefined
        }
        reactions={
          reactionRelations ? (
            <Reactions
              style={{ marginTop: config.space.S200 }}
              room={room}
              relations={reactionRelations}
              mEventId={mEventId}
              canSendReaction={canSendReaction}
              onReactionToggle={handleReactionToggle}
            />
          ) : undefined
        }
      >
        {type === MessageEvent.RoomMessageEncrypted ? (
          <EncryptedContent mEvent={mEvent}>
            {() => (
              <RenderMessageContent
                displayName={senderDisplayName}
                msgType={mEvent.getContent().msgtype ?? ''}
                ts={mEvent.getTs()}
                edited={!!editedEvent}
                getContent={getContent}
                mediaAutoLoad={mediaAutoLoad}
                urlPreview={showUrlPreview}
                htmlReactParserOptions={htmlReactParserOptions}
                linkifyOpts={linkifyOpts}
                outlineAttachment={messageLayout === MessageLayout.Bubble}
                disabledEmbedPatterns={combinedEmbedFilters}
                targetEventId={mEventId}
                onJumboEmojiClick={handleJumboEmojiClick}
              />
            )}
          </EncryptedContent>
        ) : (
          <RenderMessageContent
            displayName={senderDisplayName}
            msgType={mEvent.getContent().msgtype ?? ''}
            ts={mEvent.getTs()}
            edited={!!editedEvent}
            getContent={getContent}
            mediaAutoLoad={mediaAutoLoad}
            urlPreview={showUrlPreview}
            htmlReactParserOptions={htmlReactParserOptions}
            linkifyOpts={linkifyOpts}
            outlineAttachment={messageLayout === MessageLayout.Bubble}
            disabledEmbedPatterns={combinedEmbedFilters}
            targetEventId={mEventId}
            onJumboEmojiClick={handleJumboEmojiClick}
          />
        )}
      </Message>
    );

    return (
      <React.Fragment key={mEventId}>
        {dayDividerJSX}
        {messageJSX}
      </React.Fragment>
    );
  };

  const rootEvent = room.findEventById(threadRootId);
  const rootBody =
    getEditedEvent(threadRootId, rootEvent ?? new MatrixEvent(), roomTimelineSet)
      ?.getContent()['m.new_content']?.body ??
    rootEvent?.getContent()?.body ??
    '';
  const rootSenderId = rootEvent?.getSender() ?? '';
  const rootSenderName =
    getMemberDisplayName(room, rootSenderId) ??
    getMxIdLocalPart(rootSenderId) ??
    rootSenderId;
  // Use actual events count if thread doesn't exist but we found replies manually
  const replyCount = thread?.length ?? events.length;

  // Return as siblings rather than a wrapper so they slot into Page's flex
  // column the same way the normal RoomTimeline + RoomInput children do.
  return (
    <>
      {/* Thread sub-header */}
      <Box
        shrink="No"
        alignItems="Center"
        gap="300"
        style={{
          padding: `${config.space.S200} ${config.space.S400}`,
          borderBottom: `1px solid var(--mx-surface-container-line, rgba(0,0,0,0.08))`,
          minHeight: '3rem',
        }}
      >
        <TooltipProvider
          position="Bottom"
          offset={4}
          tooltip={
            <Tooltip>
              <Text>Back to Chat</Text>
            </Tooltip>
          }
        >
          {(triggerRef) => (
            <IconButton ref={triggerRef} size="300" onClick={handleClose}>
              <Icon src={Icons.ArrowLeft} size="200" />
            </IconButton>
          )}
        </TooltipProvider>

        <Box grow="Yes" direction="Column" style={{ overflow: 'hidden' }}>
          <Box alignItems="Center" gap="200">
            <Icon size="100" src={Icons.Thread} />
            <Text size="H5" truncate>
              Thread
            </Text>
          </Box>
          {rootBody && (
            <Text size="T200" priority="300" truncate>
              {rootSenderName}: {rootBody}
            </Text>
          )}
        </Box>

        <Text size="T200" priority="300" style={{ whiteSpace: 'nowrap' }}>
          {replyCount} {replyCount === 1 ? 'reply' : 'replies'}
        </Text>
      </Box>

      {/* Thread messages — grows to fill remaining space */}
      <Box grow="Yes" direction="Column" style={{ position: 'relative', minHeight: 0 }}>
        <Box grow="Yes" style={{ position: 'relative', minHeight: 0 }} ref={containerRef}>
          <Scroll ref={scrollRef} visibility="Hover">
            <Box
              direction="Column"
              justifyContent="End"
              style={{ minHeight: '100%', padding: `${config.space.S400} 0` }}
            >
              {/* Render the thread root event */}
              {rootEvent && renderEvent(rootEvent, -1, [])}
              
              {/* Show "no replies" message if no replies exist */}
              {events.length === 0 && (
                <Box
                  justifyContent="Center"
                  style={{ padding: config.space.S700 }}
                >
                  <Text size="T300" priority="300">
                    No replies yet. Be the first!
                  </Text>
                </Box>
              )}
              
              {/* Render all reply events */}
              {events.map((evt, idx, arr) => renderEvent(evt, idx, arr))}
            </Box>
          </Scroll>
          <EmojiConfettiOverlay
            bursts={emojiConfettiBursts}
            onBurstComplete={removeEmojiConfettiBurst}
          />
        </Box>
        <div className={roomViewCss.RoomViewBottomFloat}>
          <RoomViewTyping room={room} />
          {!hideActivity && <RoomViewFollowing room={room} />}
        </div>
      </Box>

      {/* Thread input */}
      <Box shrink="No" direction="Column">
        <RoomInput
          room={room}
          editor={editor}
          roomId={room.roomId}
          fileDropContainerRef={containerRef}
          threadRootId={threadRootId}
          ref={inputRef}
        />
      </Box>
    </>
  );
}
