// Wrapper around the chat column that adds the mobile members
// horseshoe — the group-room counterpart of `RoomViewProfilePanel`.
// Drag-down on the chat header opens the members sheet; drag-up on
// the panel closes it. Geometry is identical to the 1:1 profile
// horseshoe (same silhouette + chatBody gap + 32 px radius + Vaul
// release curve + easeInOutCubic during drag + 80 px commit),
// keeping both ends of the chat visually consistent.
//
// Tablet + Desktop branch is a pass-through; the right-side members
// pane is mounted by `Room.tsx` as a sibling via
// `RoomViewMembersSidePanel`. Mobile is the only surface that owns a
// horseshoe.
//
// Gated on the host (`Room.tsx`) — mounted for any non-1:1, non-
// callView room: group DMs, group rooms, AND channels (per design
// canon `stream-v2-dawn.jsx::ChannelDesktop`).
//
// **Profile sheet co-mount** — wherever this component mounts, the
// legacy `RoomViewProfilePanel` does NOT (the two wrappers are picked
// mutually exclusive at `Room.tsx::useMembersWrapper`). To keep tap-
// on-member-row → user-profile reachable, the same silhouette ALSO
// renders the per-user `UserRoomProfile` when `userRoomProfileAtom`
// is set. Members sheet and profile sheet are mutually exclusive at
// the atom layer (`useOpenUserRoomProfile` clears the members atom
// and vice versa), so the silhouette body just picks whichever atom
// is non-null to render. Geometry, drag mechanics and FocusTrap stay
// the same.

import React, { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useAtomValue } from 'jotai';
import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { config } from 'folds';
import { roomMembersSheetAtom } from '../../state/roomMembersSheet';
import {
  useCloseRoomMembersSheet,
  useOpenRoomMembersSheet,
} from '../../state/hooks/roomMembersSheet';
import { userRoomProfileAtom } from '../../state/userRoomProfile';
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useIsOneOnOne, useRoom } from '../../hooks/useRoom';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { MembersList } from '../../components/members-list/MembersList';
import { UserRoomProfile } from '../../components/user-profile/UserRoomProfile';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useRoomAvatar, useRoomName } from '../../hooks/useRoomMeta';
import { getMemberAvatarMxc } from '../../utils/room';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { stopPropagation } from '../../utils/keyboard';
import colorMXID from '../../../util/colorMXID';
import { VOJO_HORSESHOE_VOID_COLOR } from '../../styles/horseshoe';
import * as css from './RoomViewMembersPanel.css';

// Same constants as the profile horseshoe — see RoomViewProfilePanel.tsx
// for the rationale. Keep paired so the two surfaces stay in lockstep.
const MAX_RAIL_FRACTION = 0.85;
const COMMIT_THRESHOLD_PX = 80;
const ANIMATION_MS = 250;
const HORSESHOE_EMERGE_PX = 80;
const VAUL_EASING = 'cubic-bezier(0.32, 0.72, 0, 1)';
const easeInOutCubic = (t: number): number => (t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2);

type RoomViewMembersPanelProps = {
  header: ReactNode;
  children: ReactNode;
};

type DragState = {
  source: 'header' | 'panel';
  inputType: 'touch' | 'pointer';
  startY: number;
  deltaY: number;
};

function MobileMembersHorseshoe({ header, children }: RoomViewMembersPanelProps) {
  const { t } = useTranslation();
  const sheetState = useAtomValue(roomMembersSheetAtom);
  const profileState = useAtomValue(userRoomProfileAtom);
  const close = useCloseRoomMembersSheet();
  const closeProfile = useCloseUserRoomProfile();
  const openSheet = useOpenRoomMembersSheet();
  const room = useRoom();
  const mx = useMatrixClient();
  const isOneOnOne = useIsOneOnOne();

  // Drag-down on the chat header is the entry point for the members
  // sheet. Disabled in 1:1 rooms as defence-in-depth — this wrapper
  // shouldn't even be mounted there (1:1 owns its own profile
  // horseshoe).
  const headerDragEnabled = !isOneOnOne;

  const members = useRoomMembers(mx, room.roomId);
  const powerLevels = usePowerLevels(room);
  const creators = useRoomCreators(room);
  const getPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
  const useAuthentication = useMediaAuthentication();

  // Room-level avatar/name for the group hero AND its full-view swap.
  // No width/height passed to `mxcUrlToHttp` — Synapse returns the
  // upload at original resolution and the browser scales it via CSS.
  // Asking for a fixed thumbnail (e.g. 720x720) routes through
  // Synapse's JPEG recompression and visibly pixelates on high-DPR
  // phones (a 400 CSS px silhouette = ~1200 native px on 3x screens,
  // so a 720 thumb gets upscaled AND recompressed twice).
  // Matches `MediaViewerBody` (the other Vojo fullscreen surface).
  const roomAvatarMxc = useRoomAvatar(room, false);
  const roomAvatarUrl =
    (roomAvatarMxc && mxcUrlToHttp(mx, roomAvatarMxc, useAuthentication)) ?? undefined;
  const roomName = useRoomName(room);

  const [drag, setDrag] = useState<DragState | null>(null);

  // Two independent avatar-zoom modes that swap the panel body for a
  // silhouette-filling avatar view (`avatarFullView`). Mirrors the
  // 1:1 `MobileProfileHorseshoe.avatarMode`: full-view replaces ALL
  // card content (name, info rows, etc.) — different from the desktop
  // side-pane's inline-expand (`avatarExpanded`) which keeps the rest
  // of the card in flow below. The two modes target different bodies:
  //
  //   • `userAvatarMode`  — tap on avatar inside the embedded user
  //                         profile (member-row → profile).
  //   • `groupAvatarMode` — tap on the group hero avatar (sheet root).
  //
  // Each resets when its source identifier changes so reopening the
  // sheet / changing the viewed user doesn't land in zoom mode by
  // accident.
  const [userAvatarMode, setUserAvatarMode] = useState(false);
  useEffect(() => {
    setUserAvatarMode(false);
  }, [profileState?.userId]);
  const [groupAvatarMode, setGroupAvatarMode] = useState(false);
  useEffect(() => {
    setGroupAvatarMode(false);
  }, [sheetState?.roomId]);
  // Defensive — if the user navigates from members to user-profile (atom
  // swap), the group-avatar mode must close so the embedded profile
  // body actually shows.
  useEffect(() => {
    if (profileState) setGroupAvatarMode(false);
  }, [profileState]);
  // Mirror: if user-profile flips back to members sheet, user-avatar
  // mode must reset so the member list is visible.
  useEffect(() => {
    if (sheetState) setUserAvatarMode(false);
  }, [sheetState]);

  // Same no-thumbnail rationale as `roomAvatarUrl` above — Synapse
  // recompression visibly shakals the silhouette-filling fullview on
  // high-DPR phones. Original resolution + CSS downscale stays crisp.
  const renderUserId = profileState?.userId;
  const renderUserAvatarMxc = renderUserId ? getMemberAvatarMxc(room, renderUserId) : undefined;
  const renderUserAvatarUrl =
    (renderUserAvatarMxc && mxcUrlToHttp(mx, renderUserAvatarMxc, useAuthentication)) ?? undefined;

  const headerRef = useRef<HTMLDivElement>(null);
  const panelRef = useRef<HTMLDivElement>(null);
  const headerInnerRef = useRef<HTMLDivElement>(null);
  const panelContentRef = useRef<HTMLDivElement>(null);
  const panelScrollRef = useRef<HTMLDivElement>(null);
  const panelMeasureRef = useRef<HTMLDivElement>(null);
  const [headerNaturalHeight, setHeaderNaturalHeight] = useState(0);
  // Measured natural height of whichever body is currently rendered
  // (members list with hero, OR the embedded user profile). Drives
  // content-fit rail sizing — the sheet only opens as tall as it needs
  // to be, with a safety cap so a 200-member list doesn't fill the
  // whole screen.
  const [contentNaturalHeight, setContentNaturalHeight] = useState(0);

  // Close both sheets when the room changes — atoms are global state
  // and would otherwise carry the previous room's selection across
  // navigation. Profile gets the same cleanup because the mobile
  // group path is now the ONLY surface rendering it (the legacy
  // `MobileProfileHorseshoe` doesn't mount in group rooms).
  useEffect(
    () => () => {
      close();
      closeProfile();
    },
    [room.roomId, close, closeProfile]
  );

  const [viewportHeight, setViewportHeight] = useState(() => {
    if (typeof window === 'undefined') return 800;
    return window.innerHeight;
  });
  useEffect(() => {
    const onResize = () => setViewportHeight(window.innerHeight);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  // Hoisted ahead of the measurement effect below so its `useLayoutEffect`
  // dependency array can reference it. Re-evaluated every render — atom
  // reads above are reactive.
  const showProfile = !!profileState;

  // Rail height: cap at 85% of viewport, but shrink to the actual
  // content height when it fits. Same pattern as the 1:1 profile
  // horseshoe (`MobileProfileHorseshoe`) so the two sheets feel
  // consistent — short-content groups get a compact rail, large
  // groups cap at 85vh and the list scrolls internally.
  //
  // Measurement runs whenever the active body content changes (hero
  // height, member-list length, switching to embedded profile),
  // tracked by ResizeObserver on `panelMeasureRef`.
  useLayoutEffect(() => {
    const measureEl = panelMeasureRef.current;
    const contentEl = panelContentRef.current;
    if (!measureEl || !contentEl) return undefined;
    const measure = () => {
      const innerH = measureEl.scrollHeight;
      if (innerH <= 0) return;
      const padTop = parseFloat(getComputedStyle(contentEl).paddingTop) || 0;
      // 20 px must stay in sync with `panelHandle.height` in css.ts.
      setContentNaturalHeight(innerH + padTop + 20);
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(measureEl);
    return () => ro.disconnect();
  }, [showProfile]);

  const maxRailPx = Math.round(viewportHeight * MAX_RAIL_FRACTION);
  const railHeightPx =
    contentNaturalHeight > 0
      ? Math.min(maxRailPx, contentNaturalHeight)
      : Math.round(viewportHeight * 0.55);
  // Internal scroll only when the natural content exceeds the cap. In
  // the common fit case the panel is exactly content-sized — no
  // drag-inside-drag, no inner overscroll noise.
  const contentOverflows = contentNaturalHeight > 0 && contentNaturalHeight > maxRailPx;

  useLayoutEffect(() => {
    const el = headerInnerRef.current;
    if (!el) return undefined;
    const measure = () => {
      const next = el.scrollHeight;
      if (next > 0) setHeaderNaturalHeight(next);
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  const open = !!sheetState || showProfile;
  const baseExpanded = open ? railHeightPx : 0;
  const expandedPx = drag
    ? Math.max(0, Math.min(railHeightPx, baseExpanded + drag.deltaY))
    : baseExpanded;
  const expandedFraction = railHeightPx > 0 ? expandedPx / railHeightPx : 0;
  const isDragging = drag !== null;
  const horseshoeActive = expandedPx > 0;

  const dragRef = useRef<DragState | null>(null);
  dragRef.current = drag;
  // `openRef` tracks whether either sheet is currently visible — drag-up
  // commits use it to decide what to close.
  const openRef = useRef(open);
  openRef.current = open;
  const sheetStateRef = useRef(sheetState);
  sheetStateRef.current = sheetState;
  const profileStateRef = useRef(profileState);
  profileStateRef.current = profileState;
  const openSheetRef = useRef(openSheet);
  openSheetRef.current = openSheet;
  const closeRef = useRef(close);
  closeRef.current = close;
  const closeProfileRef = useRef(closeProfile);
  closeProfileRef.current = closeProfile;
  const roomIdRef = useRef(room.roomId);
  roomIdRef.current = room.roomId;

  useEffect(() => {
    const headerEl = headerRef.current;
    const panelEl = panelRef.current;

    const applyMove = (clientY: number, e: TouchEvent | PointerEvent) => {
      const d = dragRef.current;
      if (!d) return;
      const rawDelta = clientY - d.startY;
      let nextDelta = rawDelta;
      if (d.source === 'header') {
        nextDelta = Math.max(0, rawDelta);
      } else {
        if (rawDelta > 0) return;
        nextDelta = rawDelta;
      }
      if (e.cancelable) e.preventDefault();
      setDrag({ ...d, deltaY: nextDelta });
    };

    const applyEnd = () => {
      const d = dragRef.current;
      if (!d) return;
      if (d.source === 'header' && d.deltaY > COMMIT_THRESHOLD_PX) {
        openSheetRef.current(roomIdRef.current);
      } else if (d.source === 'panel' && -d.deltaY > COMMIT_THRESHOLD_PX) {
        // Close whichever sheet was open. Profile takes precedence
        // (it's the «foreground» sheet when both atoms are
        // hypothetically set — in practice they're mutually exclusive).
        if (profileStateRef.current) closeProfileRef.current();
        else if (sheetStateRef.current) closeRef.current();
      }
      setDrag(null);
    };

    // Scroll-aware panel drag-start: if the touch landed inside the
    // members list's scroll container AND it's not already at the top,
    // bail out so the user's swipe-up scrolls the list instead of
    // dismissing the sheet. This is the Vaul drag-handle vs scrollable-
    // body contract — without it, drag-up at any rest position closes
    // the sheet, which feels wrong on long lists.
    const panelScrollableHasScrollUp = (): boolean => {
      const el = panelScrollRef.current;
      if (!el) return false;
      return el.scrollTop > 0;
    };

    const onHeaderTouchStart = (e: TouchEvent) => {
      if (dragRef.current) return;
      if (openRef.current) return;
      if (!headerDragEnabled) return;
      const touch = e.touches[0];
      setDrag({ source: 'header', inputType: 'touch', startY: touch.clientY, deltaY: 0 });
    };

    const onPanelTouchStart = (e: TouchEvent) => {
      if (dragRef.current) return;
      if (!openRef.current) return;
      if (panelScrollableHasScrollUp()) return;
      const touch = e.touches[0];
      setDrag({ source: 'panel', inputType: 'touch', startY: touch.clientY, deltaY: 0 });
    };

    const onTouchMove = (e: TouchEvent) => {
      const d = dragRef.current;
      if (!d || d.inputType !== 'touch') return;
      applyMove(e.touches[0].clientY, e);
    };
    const onTouchEnd = () => {
      const d = dragRef.current;
      if (!d || d.inputType !== 'touch') return;
      applyEnd();
    };

    const onHeaderPointerDown = (e: PointerEvent) => {
      if (e.pointerType === 'touch') return;
      if (dragRef.current) return;
      if (openRef.current) return;
      if (!headerDragEnabled) return;
      if (e.button !== 0) return;
      setDrag({ source: 'header', inputType: 'pointer', startY: e.clientY, deltaY: 0 });
    };

    const onPanelPointerDown = (e: PointerEvent) => {
      if (e.pointerType === 'touch') return;
      if (dragRef.current) return;
      if (!openRef.current) return;
      if (panelScrollableHasScrollUp()) return;
      if (e.button !== 0) return;
      setDrag({ source: 'panel', inputType: 'pointer', startY: e.clientY, deltaY: 0 });
    };

    const onDocumentPointerMove = (e: PointerEvent) => {
      if (e.pointerType === 'touch') return;
      const d = dragRef.current;
      if (!d || d.inputType !== 'pointer') return;
      applyMove(e.clientY, e);
    };

    const onDocumentPointerEnd = (e: PointerEvent) => {
      if (e.pointerType === 'touch') return;
      const d = dragRef.current;
      if (!d || d.inputType !== 'pointer') return;
      applyEnd();
    };

    if (headerEl) {
      headerEl.addEventListener('touchstart', onHeaderTouchStart, { passive: true });
      headerEl.addEventListener('touchmove', onTouchMove, { passive: false });
      headerEl.addEventListener('touchend', onTouchEnd, { passive: true });
      headerEl.addEventListener('touchcancel', onTouchEnd, { passive: true });
      headerEl.addEventListener('pointerdown', onHeaderPointerDown);
    }
    if (panelEl) {
      panelEl.addEventListener('touchstart', onPanelTouchStart, { passive: true });
      panelEl.addEventListener('touchmove', onTouchMove, { passive: false });
      panelEl.addEventListener('touchend', onTouchEnd, { passive: true });
      panelEl.addEventListener('touchcancel', onTouchEnd, { passive: true });
      panelEl.addEventListener('pointerdown', onPanelPointerDown);
    }
    document.addEventListener('pointermove', onDocumentPointerMove, { passive: false });
    document.addEventListener('pointerup', onDocumentPointerEnd, { passive: true });
    document.addEventListener('pointercancel', onDocumentPointerEnd, { passive: true });

    return () => {
      if (headerEl) {
        headerEl.removeEventListener('touchstart', onHeaderTouchStart);
        headerEl.removeEventListener('touchmove', onTouchMove);
        headerEl.removeEventListener('touchend', onTouchEnd);
        headerEl.removeEventListener('touchcancel', onTouchEnd);
        headerEl.removeEventListener('pointerdown', onHeaderPointerDown);
      }
      if (panelEl) {
        panelEl.removeEventListener('touchstart', onPanelTouchStart);
        panelEl.removeEventListener('touchmove', onTouchMove);
        panelEl.removeEventListener('touchend', onTouchEnd);
        panelEl.removeEventListener('touchcancel', onTouchEnd);
        panelEl.removeEventListener('pointerdown', onPanelPointerDown);
      }
      document.removeEventListener('pointermove', onDocumentPointerMove);
      document.removeEventListener('pointerup', onDocumentPointerEnd);
      document.removeEventListener('pointercancel', onDocumentPointerEnd);
    };
  }, [headerDragEnabled]);

  let horseshoeRamp: number;
  if (isDragging) {
    horseshoeRamp = easeInOutCubic(Math.min(1, expandedPx / HORSESHOE_EMERGE_PX));
  } else if (horseshoeActive) {
    horseshoeRamp = 1;
  } else {
    horseshoeRamp = 0;
  }

  const silhouetteRadiusPx = horseshoeRamp * css.HORSESHOE_RADIUS_PX;
  const chatRadiusPx = horseshoeRamp * css.HORSESHOE_RADIUS_PX;
  const chatGapPx = horseshoeRamp * css.HORSESHOE_GAP_PX;

  const panelViewportTransition = isDragging ? 'none' : `height ${ANIMATION_MS}ms ${VAUL_EASING}`;
  const headerViewportTransition = isDragging ? 'none' : `height ${ANIMATION_MS}ms ${VAUL_EASING}`;
  const silhouetteTransition = isDragging
    ? 'none'
    : `border-bottom-left-radius ${ANIMATION_MS}ms ${VAUL_EASING}, border-bottom-right-radius ${ANIMATION_MS}ms ${VAUL_EASING}`;
  const chatBodyTransition = isDragging
    ? 'none'
    : `margin-top ${ANIMATION_MS}ms ${VAUL_EASING}, border-top-left-radius ${ANIMATION_MS}ms ${VAUL_EASING}, border-top-right-radius ${ANIMATION_MS}ms ${VAUL_EASING}`;

  const containerStyle: React.CSSProperties = {
    backgroundColor: horseshoeActive ? VOJO_HORSESHOE_VOID_COLOR : undefined,
  };

  // Body content selection — three mutually exclusive branches. Hoisted
  // here so the JSX below doesn't need a nested ternary (eslint
  // `no-nested-ternary`).
  let panelBody: ReactNode;
  if (userAvatarMode && renderUserId) {
    // User-profile avatar full-view: silhouette-filling swap. Mirrors
    // `MobileProfileHorseshoe.avatarMode` exactly. NOT the desktop
    // side-pane `avatarExpanded` inline-expand path (which would push
    // the name/info rows into the rail and read as a broken layout).
    panelBody = (
      <button
        type="button"
        className={css.avatarFullView}
        onClick={() => setUserAvatarMode(false)}
        aria-label={t('Room.collapse_avatar')}
      >
        {renderUserAvatarUrl ? (
          <img
            className={css.avatarFullImage}
            src={renderUserAvatarUrl}
            alt={renderUserId}
            draggable={false}
          />
        ) : (
          <div
            className={css.avatarFullFallback}
            style={{ backgroundColor: colorMXID(renderUserId) }}
          >
            {(getMxIdLocalPart(renderUserId)?.[0] ?? '?').toUpperCase()}
          </div>
        )}
      </button>
    );
  } else if (groupAvatarMode) {
    // Group hero avatar full-view — same pattern, source is the room.
    panelBody = (
      <button
        type="button"
        className={css.avatarFullView}
        onClick={() => setGroupAvatarMode(false)}
        aria-label={t('Room.collapse_avatar')}
      >
        {roomAvatarUrl ? (
          <img
            className={css.avatarFullImage}
            src={roomAvatarUrl}
            alt={roomName}
            draggable={false}
          />
        ) : (
          <div
            className={css.avatarFullFallback}
            style={{ backgroundColor: colorMXID(room.roomId) }}
          >
            {(roomName?.[0] ?? '?').toUpperCase()}
          </div>
        )}
      </button>
    );
  } else {
    panelBody = (
      <div className={css.panelInner}>
        <div
          ref={panelScrollRef}
          className={css.panelScroll}
          style={{ overflowY: contentOverflows ? 'auto' : 'hidden' }}
        >
          <div ref={panelMeasureRef}>
            {showProfile && profileState ? (
              <div style={{ padding: config.space.S400 }}>
                <UserRoomProfile
                  userId={profileState.userId}
                  onAvatarClick={() => setUserAvatarMode(true)}
                />
              </div>
            ) : (
              <MembersList
                room={room}
                members={members}
                getPowerTag={getPowerTag}
                onHeroAvatarClick={() => setGroupAvatarMode(true)}
              />
            )}
          </div>
        </div>
        <div className={css.panelHandle} aria-label={t('Room.drag_to_close')}>
          <div className={css.panelHandleBar} />
        </div>
      </div>
    );
  }

  return (
    <div className={css.container} style={containerStyle}>
      <div
        className={css.silhouette}
        style={{
          borderBottomLeftRadius: `${silhouetteRadiusPx}px`,
          borderBottomRightRadius: `${silhouetteRadiusPx}px`,
          transition: silhouetteTransition,
        }}
      >
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            clickOutsideDeactivates: false,
            allowOutsideClick: () => true,
            escapeDeactivates: stopPropagation,
            onDeactivate: () => {
              // Mirror manual close — let users hit Esc to dismiss the
              // sheet even on a hybrid keyboard / touch device.
              if (profileStateRef.current) closeProfileRef.current();
              else if (sheetStateRef.current) closeRef.current();
            },
            checkCanFocusTrap: () => Promise.resolve(),
          }}
          active={open}
        >
          <div
            ref={panelRef}
            className={css.panelViewport}
            style={{
              height: `${expandedPx}px`,
              transition: panelViewportTransition,
              visibility: expandedPx > 0 ? 'visible' : 'hidden',
            }}
          >
            <div
              ref={panelContentRef}
              className={css.panelContent}
              style={{ height: `${railHeightPx}px` }}
            >
              {panelBody}
            </div>
          </div>
        </FocusTrap>

        <div
          ref={headerRef}
          className={css.headerViewport}
          style={{
            height:
              headerNaturalHeight > 0
                ? `${(1 - expandedFraction) * headerNaturalHeight}px`
                : 'auto',
            transition: headerViewportTransition,
            touchAction: headerDragEnabled ? 'pan-x' : undefined,
          }}
        >
          <div ref={headerInnerRef} className={css.headerViewportInner}>
            {header}
          </div>
        </div>
      </div>

      <div
        className={css.chatBody}
        style={{
          marginTop: `${chatGapPx}px`,
          borderTopLeftRadius: chatRadiusPx ? `${chatRadiusPx}px` : undefined,
          borderTopRightRadius: chatRadiusPx ? `${chatRadiusPx}px` : undefined,
          overflow: chatRadiusPx > 0 ? 'hidden' : undefined,
          transition: chatBodyTransition,
          overscrollBehaviorY: 'contain',
        }}
      >
        {children}
      </div>
    </div>
  );
}

export function RoomViewMembersPanel({ header, children }: RoomViewMembersPanelProps) {
  const isMobile = useScreenSizeContext() === ScreenSize.Mobile;
  if (!isMobile) {
    return (
      <>
        {header}
        {children}
      </>
    );
  }
  return <MobileMembersHorseshoe header={header}>{children}</MobileMembersHorseshoe>;
}
