import { useEffect, useRef, useState, useMemo, useCallback, memo } from "react";
import { useMessageStore } from "../stores/message.ts";
import { api } from "../lib/api.ts";
import { useChannelStore } from "../stores/channel.ts";
import { useServerStore } from "../stores/server.ts";
import { useTypingStore } from "../stores/typing.ts";
import { useAuthStore } from "../stores/auth.ts";
import { useMemberStore } from "../stores/member.ts";
import { useThreadStore } from "../stores/thread.ts";
import { GiphyPicker, type Gif } from "./GiphyPicker";
import { CommandDropdown } from "./CommandDropdown";
import { findCommand } from "../lib/slashCommands";
import { PollDisplay, CreatePollModal } from "./Poll.tsx";
import { MentionDropdown } from "./MentionDropdown";
import { useReadStatesStore } from "../stores/readStates.ts";
import { MessageSearch } from "./MessageSearch";
import { ThreadListPanel } from "./ThreadListPanel.tsx";
import { ThreadPanel } from "./ThreadPanel.tsx";
import { useContextMenu } from "./ContextMenu.tsx";
import { PinnedMessages } from "./PinnedMessages.tsx";
import { ReactionBar } from "./ReactionBar.tsx";
import { EmojiPicker } from "./EmojiPicker.tsx";
import { ReplyBar } from "./ReplyBar.tsx";
import { ForumView } from "./ForumView.tsx";
import { CalendarView } from "./CalendarView.tsx";
import { DocsView } from "./DocsView.tsx";
import { ListView } from "./ListView.tsx";
import { UserProfileModal } from "./UserProfileModal.tsx";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

function formatTime(iso: string): string {
  const date = new Date(iso);
  const day = date.getDate().toString().padStart(2, "0");
  const month = (date.getMonth() + 1).toString().padStart(2, "0");
  const year = date.getFullYear();
  const hours = date.getHours().toString().padStart(2, "0");
  const minutes = date.getMinutes().toString().padStart(2, "0");
  return `[${day}.${month}.${year} @ ${hours}:${minutes}]`;
}

function renderContent(content: string, memberUsernames: Set<string>) {
  const segments: { type: "text" | "mention"; value: string }[] = [];
  const mentionRe = /@([a-zA-Z0-9_.-]+)/g;
  let last = 0;
  let match: RegExpExecArray | null;
  while ((match = mentionRe.exec(content)) !== null) {
    if (match.index > last) {
      segments.push({ type: "text", value: content.slice(last, match.index) });
    }
    const username = match[1];
    if (memberUsernames.has(username)) {
      segments.push({ type: "mention", value: username });
    } else {
      segments.push({ type: "text", value: match[0] });
    }
    last = mentionRe.lastIndex;
  }
  if (last < content.length) {
    segments.push({ type: "text", value: content.slice(last) });
  }

  return segments.map((seg, idx) => {
    if (seg.type === "mention") {
      // ponytail: add trailing space because React collapses whitespace between sibling spans
      const nextSeg = segments[idx + 1];
      const needsSpace = !nextSeg || (nextSeg.type === "text" && !nextSeg.value.startsWith(" "));
      return (
        <span key={idx} className="text-gb-aqua">
          @{seg.value}{needsSpace ? " " : ""}
        </span>
      );
    }
    // For plain text segments, render through ReactMarkdown but preserve
    // leading/trailing spaces that ReactMarkdown would otherwise trim
    const leadingSpace = seg.value.match(/^\s+/)?.[0] || "";
    const trailingSpace = seg.value.match(/\s+$/)?.[0] || "";
    const trimmed = seg.value.slice(leadingSpace.length, seg.value.length - trailingSpace.length);

    return (
      <span key={idx}>
        {leadingSpace}
        {trimmed ? (
          <ReactMarkdown
            remarkPlugins={[remarkGfm]}
            components={{
              a: ({ ...props }) => <a {...props} className="text-gb-aqua hover:underline" target="_blank" rel="noreferrer" />,
              code: ({ ...props }) => <code {...props} className="bg-gb-bg-t px-1 rounded text-gb-fg-s" />,
              pre: ({ ...props }) => <pre {...props} className="bg-gb-bg-s p-2 my-1 overflow-x-auto text-gb-fg-s" />,
              blockquote: ({ ...props }) => <blockquote {...props} className="border-l-2 border-gb-orange pl-2 my-1 text-gb-fg-s" />,
              table: ({ ...props }) => <table {...props} className="border-collapse border border-gb-bg-t my-1" />,
              th: ({ ...props }) => <th {...props} className="border border-gb-bg-t px-2 py-1 bg-gb-bg-s" />,
              td: ({ ...props }) => <td {...props} className="border border-gb-bg-t px-2 py-1" />,
              p: ({ ...props }) => <span {...props} className="inline" />,
              img: ({ src, ...props }) => {
                const finalSrc = src?.startsWith("https://media") && src.includes(".giphy.com/")
                  ? `/api/v1/gifs/proxy?url=${encodeURIComponent(src)}`
                  : src;
                return (
                  <img
                    {...props}
                    src={finalSrc}
                    className="max-w-[240px] max-h-[240px] object-contain rounded my-1 block"
                    loading="lazy"
                  />
                );
              },
            }}
          >
            {trimmed}
          </ReactMarkdown>
        ) : null}
        {trailingSpace}
      </span>
    );
  });
}

function renderEmbeds(embeds?: { url: string; title?: string; description?: string; image_url?: string; site_name?: string }[]) {
  if (!embeds || embeds.length === 0) return null;
  return (
    <div className="mt-1 space-y-1">
      {embeds.map((embed) => (
        <a
          key={embed.url}
          href={embed.url}
          target="_blank"
          rel="noreferrer"
          className="block bg-gb-bg-s border border-gb-bg-t p-2 hover:border-gb-fg-t transition-colors"
        >
          {embed.site_name && <div className="text-gb-fg-f text-[10px] uppercase">{embed.site_name}</div>}
          {embed.title && <div className="text-gb-fg text-xs font-bold truncate">{embed.title}</div>}
          {embed.description && <div className="text-gb-fg-s text-xs line-clamp-2">{embed.description}</div>}
          {embed.image_url && (
            <img
              src={embed.image_url}
              alt=""
              className="mt-1 max-h-24 object-cover border border-gb-bg-t"
              loading="lazy"
            />
          )}
        </a>
      ))}
    </div>
  );
}

const MessageItem = memo(({
  message,
  memberUsernames,
  selectMode,
  isSelected,
  onAuthorClick,
  onToggleSelect,
  onContextMenu,
  onAddReaction,
  currentUserId,
  activeReactionMessageId,
  setActiveReactionMessageId,
  members,
  parentMessage,
  onJumpToParent,
}: {
  message: any;
  memberUsernames: Set<string>;
  selectMode: boolean;
  isSelected: boolean;
  onAuthorClick: (authorId: string) => void;
  onToggleSelect: (messageId: string) => void;
  onContextMenu: (e: React.MouseEvent, message: any) => void;
  onAddReaction: (messageId: string, emoji: string) => void;
  currentUserId?: string;
  activeReactionMessageId: string | null;
  setActiveReactionMessageId: (id: string | null) => void;
  members: any[];
  parentMessage: any | null;
  onJumpToParent: (id: string) => void;
}) => {
  return (
    <div
      id={`msg-${message.id}`}
      onClick={() => selectMode && onToggleSelect(message.id)}
      onContextMenu={(e) => onContextMenu(e, message)}
      className={`break-words ${selectMode ? 'cursor-pointer hover:bg-gb-bg-s' : ''} ${isSelected ? 'bg-gb-bg-s' : ''} relative`}
    >
      {parentMessage && (
        <div
          onClick={(e) => {
            e.stopPropagation();
            onJumpToParent(parentMessage.id);
          }}
          className="cursor-pointer hover:opacity-80 mb-0.5"
        >
          <ReplyBar
            replyTo={{
              id: parentMessage.id,
              content: parentMessage.content,
              author: {
                username: parentMessage.author_username,
              }
            }}
            compact
          />
        </div>
      )}
      {selectMode && (
        <span className="text-gb-fg-f mr-2">
          {isSelected ? '[x]' : '[ ]'}
        </span>
      )}
      <span className="text-gb-fg-f">{formatTime(message.created_at)}</span>{' '}
      {message.pinned && <span className="text-gb-orange font-bold mr-1">[PIN]</span>}
      <span className="text-gb-aqua hover:text-gb-orange cursor-pointer" onClick={(e) => {
        e.stopPropagation();
        onAuthorClick(message.author_id);
      }}>
        &lt;{members.find((m) => m.id === message.author_id)?.nickname || message.author_username}&gt;
      </span>{" "}
      <span className="text-gb-fg">{renderContent(message.content, memberUsernames)}</span>
      {renderEmbeds(message.embeds)}
      {message.poll && <PollDisplay poll={message.poll} channelId={message.channel_id} />}

      {/* Message reactions */}
      <ReactionBar
        messageId={message.id}
        reactions={(message.reactions || []).map((r: any) => {
          const usernames = r.users.map((uid: string) => {
            const m = members.find((member: any) => member.id === uid);
            return m ? (m.display_name || m.username) : "unknown user";
          });
          return {
            emoji: r.emoji,
            count: r.count,
            users: usernames,
            reacted: r.users.includes(currentUserId),
          };
        })}
        onRefresh={() => {}}
      />

      {/* Kaomoji picker popover */}
      {activeReactionMessageId === message.id && (
        <EmojiPicker
          onSelect={(emoji) => onAddReaction(message.id, emoji)}
          onClose={() => setActiveReactionMessageId(null)}
        />
      )}
    </div>
  );
});
MessageItem.displayName = "MessageItem";

export function ChatArea() {
  const activeChannelId = useChannelStore((s) => s.activeChannelId);
  const channelsByServer = useChannelStore((s) => s.channelsByServer);
  const activeServerId = useServerStore((s) => s.activeServerId);
  const messages = useMessageStore((s) =>
    activeChannelId ? s.messagesByChannel[activeChannelId] || [] : [],
  );
  const isLoading = useMessageStore((s) => s.isLoading);
  const isLoadingOlder = useMessageStore((s) => s.isLoadingOlder);
  const fetchMessages = useMessageStore((s) => s.fetchMessages);
  const fetchOlderMessages = useMessageStore((s) => s.fetchOlderMessages);
  const sendMessage = useMessageStore((s) => s.sendMessage);
  const typingUsers = useTypingStore((s) => s.typingUsers);
  const sendTypingStart = useTypingStore((s) => s.sendTypingStart);
  const currentUser = useAuthStore((s) => s.user);
  const membersByServer = useMemberStore((s) => s.membersByServer);
  const threads = useThreadStore((s) => activeChannelId ? s.threadsByParent[activeChannelId] || [] : []);
  const [input, setInput] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [showGifPicker, setShowGifPicker] = useState(false);
  const [showKaomoji, setShowKaomoji] = useState(false);
  const [showSearch, setShowSearch] = useState(false);
  const [mentionQuery, setMentionQuery] = useState<string | null>(null);
  const [commandQuery, setCommandQuery] = useState<string | null>(null);
  const [showPollModal, setShowPollModal] = useState(false);
  const [slowmodeRemaining, setSlowmodeRemaining] = useState(0);
  const [selectMode, setSelectMode] = useState(false);
  const [showThreads, setShowThreads] = useState(false);
  const [activeThread, setActiveThread] = useState<{ id: string; name: string } | null>(null);
  const [profileUserId, setProfileUserId] = useState<string | null>(null);
  const bottomRef = useRef<HTMLDivElement>(null);
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const lastTypingRef = useRef<number>(0);

  const toggleSelectedMessage = useMessageStore((s) => s.toggleSelectedMessage);
  const clearSelectedMessages = useMessageStore((s) => s.clearSelectedMessages);
  const bulkDeleteMessages = useMessageStore((s) => s.bulkDeleteMessages);
  const selectedIds = useMessageStore((s) =>
    activeChannelId ? s.selectedMessageIds[activeChannelId] || new Set<string>() : new Set<string>(),
  );
  const canBulkDelete = true;

  const channels = activeServerId ? channelsByServer[activeServerId] || [] : [];
  const activeChannel = channels.find((c) => c.id === activeChannelId);
  const members = activeServerId ? membersByServer[activeServerId] || [] : [];
  const memberUsernames = useMemo(() => new Set(members.map((m) => m.username)), [members]);
  const markRead = useReadStatesStore((s) => s.markRead);

  const [showPinned, setShowPinned] = useState(false);
  const [activeReactionMessageId, setActiveReactionMessageId] = useState<string | null>(null);
  const [replyToMessage, setReplyToMessage] = useState<any | null>(null);
  const pinMessage = useMessageStore((s) => s.pinMessage);
  const unpinMessage = useMessageStore((s) => s.unpinMessage);
  const fetchPinnedMessages = useMessageStore((s) => s.fetchPinnedMessages);
  const pinnedMessages = useMessageStore((s) => activeChannelId ? s.pinnedMessagesByChannel[activeChannelId] || [] : []);
  const pinnedCount = pinnedMessages.length;

  const { showMenu, MenuPortal } = useContextMenu();

  const handleJumpToMessage = useCallback((messageId: string) => {
    const el = document.getElementById(`msg-${messageId}`);
    if (el) {
      el.scrollIntoView({ behavior: "smooth", block: "center" });
      el.classList.add("terminal-mention");
      setTimeout(() => {
        el.classList.remove("terminal-mention");
      }, 2000);
    }
  }, []);

  const handleAddReaction = useCallback(async (messageId: string, emoji: string) => {
    try {
      await api.post(`/messages/${messageId}/reactions`, { emoji });
      setActiveReactionMessageId(null);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to add reaction");
    }
  }, []);

  const handleMessageContextMenu = useCallback((e: React.MouseEvent, msg: any) => {
    if (selectMode) return;
    showMenu(e, [
      {
        label: "[REPLY]",
        onClick: () => {
          setReplyToMessage(msg);
        }
      },
      {
        label: "[ADD REACTION]",
        onClick: () => {
          setActiveReactionMessageId(msg.id);
        }
      },
      {
        label: msg.pinned ? "[UNPIN MESSAGE]" : "[PIN MESSAGE]",
        onClick: async () => {
          try {
            if (msg.pinned) {
              await unpinMessage(msg.channel_id, msg.id);
            } else {
              await pinMessage(msg.channel_id, msg.id);
            }
          } catch (err) {
            setError(err instanceof Error ? err.message : "Pin operation failed");
          }
        }
      }
    ]);
  }, [selectMode, showMenu, pinMessage, unpinMessage]);

  useEffect(() => {
    if (activeChannelId) {
      fetchPinnedMessages(activeChannelId);
    }
  }, [activeChannelId, fetchPinnedMessages]);

  const handleToggleSelect = useCallback((messageId: string) => {
    if (activeChannelId) {
      toggleSelectedMessage(activeChannelId, messageId);
    }
  }, [activeChannelId, toggleSelectedMessage]);

  const handleAuthorClick = useCallback((authorId: string) => {
    setProfileUserId(authorId);
  }, []);

  useEffect(() => {
    if (activeChannelId) {
      fetchMessages(activeChannelId);
      setError(null);
    }
  }, [activeChannelId, fetchMessages]);

  useEffect(() => {
    if (!activeChannelId || messages.length === 0 || isLoading) return;
    const lastMsg = messages[messages.length - 1];
    if (lastMsg?.id) {
      markRead(activeChannelId, lastMsg.id);
    }
  }, [activeChannelId, messages.length, isLoading]);

  const handleScroll = useCallback(() => {
    const el = scrollContainerRef.current;
    if (!el || !activeChannelId || isLoadingOlder) return;
    if (el.scrollTop < 100) {
      const prevHeight = el.scrollHeight;
      fetchOlderMessages(activeChannelId).then(() => {
        // ponytail: maintain scroll position after prepending older messages
        requestAnimationFrame(() => {
          el.scrollTop = el.scrollHeight - prevHeight;
        });
      });
    }
  }, [activeChannelId, isLoadingOlder, fetchOlderMessages]);

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: "auto" });
  }, [messages]);

  useEffect(() => {
    if (slowmodeRemaining <= 0) return;
    const t = setInterval(() => {
      setSlowmodeRemaining((r) => {
        if (r <= 1) {
          clearInterval(t);
          return 0;
        }
        return r - 1;
      });
    }, 1000);
    return () => clearInterval(t);
  }, [slowmodeRemaining]);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    const cursor = e.target.selectionStart ?? value.length;
    setInput(value);

    // ponytail: throttle typing indicator to once per 3s
    if (activeChannelId && value.length > 0) {
      const now = Date.now();
      if (now - lastTypingRef.current > 3000) {
        sendTypingStart(activeChannelId);
        lastTypingRef.current = now;
      }
    }

    const beforeCursor = value.slice(0, cursor);
    // Slash command detection (only at start of input)
    if (beforeCursor.startsWith('/') && !beforeCursor.includes(' ')) {
      setCommandQuery(beforeCursor.slice(1));
      setMentionQuery(null);
      return;
    }
    setCommandQuery(null);
    const atIndex = beforeCursor.lastIndexOf("@");
    if (atIndex === -1) {
      setMentionQuery(null);
      return;
    }
    const between = beforeCursor.slice(atIndex + 1);
    if (between.includes(" ") || between.includes("\n")) {
      setMentionQuery(null);
      return;
    }
    setMentionQuery(between);
  };

  const handleMentionSelect = (username: string) => {
    const inputEl = inputRef.current;
    if (!inputEl) {
      setInput((prev) => `${prev}${username} `);
      setMentionQuery(null);
      return;
    }
    // Read directly from DOM to avoid stale closure
    const currentValue = inputEl.value;
    const cursor = inputEl.selectionStart ?? currentValue.length;
    const beforeCursor = currentValue.slice(0, cursor);
    const atIndex = beforeCursor.lastIndexOf("@");
    if (atIndex === -1) return;
    const before = currentValue.slice(0, atIndex);
    const after = currentValue.slice(cursor);
    const separator = after.startsWith(" ") || after === "" ? "" : " ";
    const next = `${before}@${username}${separator}${after}`;
    setInput(next);
    setMentionQuery(null);
    // Also set the DOM value immediately so cursor positioning works
    inputEl.value = next;
    const pos = atIndex + username.length + 2;
    inputEl.focus();
    inputEl.setSelectionRange(pos, pos);
  };

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    if (!activeChannelId || !input.trim() || slowmodeRemaining > 0) return;
    setError(null);
    try {
      let messageText = input.trim();
      // Slash command transform
      if (messageText.startsWith('/')) {
        const spaceIdx = messageText.indexOf(' ');
        const cmdName = spaceIdx === -1 ? messageText.slice(1) : messageText.slice(1, spaceIdx);
        const args = spaceIdx === -1 ? '' : messageText.slice(spaceIdx + 1).trim();
        // /poll opens the poll creation modal
        if (cmdName === 'poll') {
          setShowPollModal(true);
          setInput('');
          setCommandQuery(null);
          return;
        }
        const cmd = findCommand(cmdName);
        if (cmd) {
          messageText = cmd.transform(args, currentUser?.username || 'user');
        }
      }
      await sendMessage(activeChannelId, messageText, replyToMessage?.id);
      setInput("");
      setReplyToMessage(null);
      setMentionQuery(null);
    } catch (err) {
      const msg = err instanceof Error ? err.message : "Failed to send";
      try {
        const parsed = JSON.parse(msg);
        if (parsed.error === "slowmode" && typeof parsed.retry_after === "number") {
          setSlowmodeRemaining(parsed.retry_after);
          return;
        }
      } catch {
        // not json
      }
      setError(msg);
    }
  };

  const handleGifSelect = async (gif: Gif) => {
    if (!activeChannelId) return;
    const content = `![${gif.title || "GIF"}](${gif.images.fixed_height.url})`;
    try {
      await sendMessage(activeChannelId, content);
      setShowGifPicker(false);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to send");
    }
  };

  return (
    <div className="flex flex-col h-full bg-gb-bg">
      <div className="terminal-border border-t-0 border-x-0 px-3 py-2 text-gb-fg-s flex items-center justify-between">
        <span>
          {activeChannel
            ? activeChannel.type === 'forum'
              ? `■ ${activeChannel.name}`
              : activeChannel.type === 'calendar'
                ? `○ ${activeChannel.name}`
                : activeChannel.type === 'docs'
                  ? `☰ ${activeChannel.name}`
                  : activeChannel.type === 'list'
                    ? `☑ ${activeChannel.name}`
                    : `# ${activeChannel.name}`
            : activeChannelId
              ? `#${activeChannelId}`
              : "[NO CHANNEL SELECTED]"}
        </span>
        {activeChannelId && activeChannel?.type === 'text' && (
          <div className="flex items-center gap-2">
            <button
              onClick={() => setShowThreads((prev) => !prev)}
              className={`text-xs font-mono ${showThreads ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
              title="Toggle threads panel"
            >
              [THREADS{threads.length > 0 ? `:${threads.length}` : ''}]
            </button>
            <button
              onClick={() => setSelectMode((prev) => !prev)}
              className={`text-xs font-mono ${selectMode ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
              title="Toggle bulk delete selection"
            >
              [SELECT]
            </button>
            <button
              onClick={() => {
                setShowPinned(false);
                setShowSearch((prev) => !prev);
              }}
              className={`text-xs font-mono ${showSearch ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
              title="Search messages"
            >
              [SEARCH]
            </button>
            <button
              onClick={() => {
                setShowSearch(false);
                setShowPinned((prev) => !prev);
              }}
              className={`text-xs font-mono ${showPinned ? 'text-gb-orange' : 'text-gb-fg-f hover:text-gb-orange'}`}
              title="Show pinned messages"
            >
              [PINNED{pinnedCount > 0 ? `:${pinnedCount}` : ''}]
            </button>
          </div>
        )}
      </div>

      {selectMode && activeChannelId && activeChannel?.type !== 'forum' && (
        <div className="px-3 py-1 text-xs font-mono text-gb-fg-f flex items-center justify-between bg-gb-bg-s border-b border-gb-bg-t">
          <span>{selectedIds.size} selected</span>
          <div className="flex items-center gap-2">
            <button
              onClick={() => clearSelectedMessages(activeChannelId)}
              className="text-gb-fg-f hover:text-gb-orange"
            >
              [CLEAR]
            </button>
            <button
              onClick={async () => {
                if (!activeChannelId || selectedIds.size === 0) return;
                if (!confirm(`Delete ${selectedIds.size} messages?`)) return;
                try {
                  await bulkDeleteMessages(activeChannelId, Array.from(selectedIds));
                } catch (err) {
                  setError(err instanceof Error ? err.message : 'Bulk delete failed');
                }
              }}
              disabled={selectedIds.size === 0 || !canBulkDelete}
              className="text-gb-red hover:text-gb-orange disabled:opacity-50"
            >
              [DELETE]
            </button>
          </div>
        </div>
      )}
      <div className="flex flex-1 min-h-0">
        {activeChannel && activeChannel.type === 'forum' && activeChannelId ? (
          <ForumView channelId={activeChannelId} channelName={activeChannel.name} onOpenThread={(t) => setActiveThread(t)} />
        ) : activeChannel && activeChannel.type === 'calendar' && activeChannelId ? (
          <CalendarView channelId={activeChannelId} channelName={activeChannel.name} />
        ) : activeChannel && activeChannel.type === 'docs' && activeChannelId ? (
          <DocsView channelId={activeChannelId} channelName={activeChannel.name} />
        ) : activeChannel && activeChannel.type === 'list' && activeChannelId ? (
          <ListView channelId={activeChannelId} channelName={activeChannel.name} />
        ) : (
          <>
            <div className="flex-1 flex flex-col min-w-0 relative">
              {showSearch && activeChannelId && activeChannel && (
                <MessageSearch
                  channelId={activeChannelId}
                  channelName={activeChannel.name}
                  onClose={() => setShowSearch(false)}
                  onJump={handleJumpToMessage}
                />
              )}
              {showPinned && activeChannelId && activeChannel && (
                <PinnedMessages
                  channelId={activeChannelId}
                  channelName={activeChannel.name}
                  onClose={() => setShowPinned(false)}
                  onJump={handleJumpToMessage}
                />
              )}
              {showThreads && activeChannelId && activeChannel?.type !== 'forum' && (
                <ThreadListPanel
                  channelId={activeChannelId}
                  onSelect={(t: { id: string; name: string }) => setActiveThread(t)}
                  onClose={() => setShowThreads(false)}
                />
              )}
              <div ref={scrollContainerRef} onScroll={handleScroll} className="flex-1 overflow-y-auto p-3 space-y-1 font-mono text-sm">
                {isLoadingOlder && <p className="text-center text-gb-fg-f text-xs">[loading older messages...]</p>}
                {isLoading && <p className="text-gb-fg-f">[loading...]</p>}
                {!isLoading && messages.length === 0 && (
                  <p className="text-gb-fg-f">[no messages in this channel]</p>
                )}
                {messages.map((message) => (
                  <MessageItem
                    key={message.id}
                    message={message}
                    memberUsernames={memberUsernames}
                    selectMode={selectMode}
                    isSelected={selectedIds.has(message.id)}
                    onAuthorClick={handleAuthorClick}
                    onToggleSelect={handleToggleSelect}
                    onContextMenu={handleMessageContextMenu}
                    onAddReaction={handleAddReaction}
                    currentUserId={currentUser?.id}
                    activeReactionMessageId={activeReactionMessageId}
                    setActiveReactionMessageId={setActiveReactionMessageId}
                    members={members}
                    parentMessage={message.reply_to ? messages.find((m) => m.id === message.reply_to) : null}
                    onJumpToParent={handleJumpToMessage}
                  />
                ))}
                <div ref={bottomRef} />
              </div>
              {showGifPicker && (
                <div className="px-3 pb-1">
                  <GiphyPicker onSelect={handleGifSelect} onClose={() => setShowGifPicker(false)} />
                </div>
              )}
              <div className="px-3 pt-1 text-xs text-gb-fg-f font-mono italic h-5 select-none">
                {(() => {
                  const chTyping = activeChannelId ? (typingUsers[activeChannelId] || []).filter(u => u.userId !== currentUser?.id) : [];
                  if (chTyping.length === 0) return "\u00A0";
                  const names = chTyping.map(u => u.username);
                  return names.length === 1 ? `${names[0]} is typing...` : names.length === 2 ? `${names[0]} and ${names[1]} are typing...` : `${names[0]} and ${names.length - 1} others are typing...`;
                })()}
              </div>
              {replyToMessage && (
                <div className="px-3 pb-1">
                  <ReplyBar
                    replyTo={{
                      id: replyToMessage.id,
                      content: replyToMessage.content,
                      author: {
                        username: replyToMessage.author_username,
                      }
                    }}
                    onCancel={() => setReplyToMessage(null)}
                  />
                </div>
              )}
              {error && (
                <div className="px-3 pb-1">
                  <p className="text-gb-red text-xs font-mono">ERR: {error}</p>
                </div>
              )}
              {slowmodeRemaining > 0 && (
                <div className="px-3 pt-1 text-xs text-gb-fg-f font-mono">
                  SLOWMODE: wait {slowmodeRemaining}s
                </div>
              )}
              <form onSubmit={handleSubmit} className="p-3 flex items-center gap-2 relative">
                <span className="text-gb-fg-f select-none shrink-0">{">"}</span>
                <div className="flex-1 min-w-0 relative">
                  <input
                    ref={inputRef}
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="type a message..."
                    className="terminal-input w-full"
                    disabled={!activeChannelId || slowmodeRemaining > 0}
                    onKeyDown={(e) => {
                      if (e.ctrlKey && e.key === 'e') {
                        e.preventDefault();
                        setShowKaomoji((prev) => !prev);
                      }
                    }}
                  />
                  {mentionQuery !== null && (
                    <MentionDropdown query={mentionQuery} members={members} onSelect={handleMentionSelect} />
                  )}
                  {commandQuery !== null && (
                    <CommandDropdown
                      query={commandQuery}
                      onSelect={(name) => {
                        setInput('/' + name + ' ');
                        setCommandQuery(null);
                        inputRef.current?.focus();
                      }}
                    />
                  )}
                </div>
                <button
                  type="button"
                  onClick={() => setShowGifPicker((prev) => !prev)}
                  disabled={!activeChannelId}
                  className="text-gb-fg-f hover:text-gb-orange font-mono text-sm select-none disabled:opacity-50 disabled:cursor-not-allowed shrink-0"
                  title="Toggle GIF picker"
                >
                  [GIF]
                </button>
                <button
                  type="button"
                  onClick={() => setShowKaomoji((prev) => !prev)}
                  disabled={!activeChannelId}
                  className="text-gb-fg-f hover:text-gb-orange font-mono text-sm select-none disabled:opacity-50 disabled:cursor-not-allowed shrink-0"
                  title="Toggle kaomoji picker (Ctrl+E)"
                >
                  [☺]
                </button>
                {showKaomoji && (
                  <EmojiPicker
                    onSelect={(emoji) => setInput((prev) => prev + emoji)}
                    onClose={() => setShowKaomoji(false)}
                  />
                )}
              </form>
            </div>
            {activeThread && (
              <ThreadPanel threadId={activeThread.id} threadName={activeThread.name} onClose={() => setActiveThread(null)} />
            )}
          </>
        )}
      </div>
      {profileUserId && (
        <UserProfileModal userId={profileUserId} onClose={() => setProfileUserId(null)} />
      )}
      {MenuPortal}
      {showPollModal && activeChannelId && (
        <CreatePollModal
          channelId={activeChannelId}
          onClose={() => setShowPollModal(false)}
        />
      )}
    </div>
  );
}
