import React, { useCallback } from 'react';
import { Box, Line, toRem } from 'folds';
import { useMatch, useParams } from 'react-router-dom';
import { isKeyHotkey } from 'is-hotkey';
import { useAtomValue } from 'jotai';
import { RoomView } from './RoomView';
import { userRoomProfileAtom } from '../../state/userRoomProfile';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { VOJO_HORSESHOE_GAP_PX, VOJO_HORSESHOE_VOID_COLOR } from '../../styles/horseshoe';
import { ThreadDrawer } from './ThreadDrawer';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { PowerLevelsContextProvider, usePowerLevels } from '../../hooks/usePowerLevels';
import { useIsOneOnOne, useRoom } from '../../hooks/useRoom';
import { useKeyDown } from '../../hooks/useKeyDown';
import { markAsRead } from '../../utils/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { CallView } from '../call/CallView';
import { RoomViewHeader } from './RoomViewHeader';
import { callChatAtom } from '../../state/callEmbed';
import { CallChatView } from './CallChatView';
import { RoomViewProfilePanel } from './RoomViewProfilePanel';
import { RoomViewProfileSidePanel } from './RoomViewProfileSidePanel';
import { RoomViewMembersPanel } from './RoomViewMembersPanel';
import { RoomViewMembersSidePanel } from './RoomViewMembersSidePanel';
import { MobileMediaViewerHorseshoe } from './MobileMediaViewerHorseshoe';
import { RoomViewMediaWindow } from './RoomViewMediaWindow';
import { MediaViewerHostContext } from './mediaViewerHostContext';
import { roomMembersSheetAtom } from '../../state/roomMembersSheet';
import { useChannelsMode, ThreadDrawerOpenProvider } from '../../hooks/useChannelsMode';
import { CHANNELS_THREAD_PATH } from '../../pages/paths';
import { getChannelsRoomPath } from '../../pages/pathUtils';
import { isBridgedRoom } from '../../utils/room';
import { useUnreadThreadingEnabled } from '../../hooks/useClientConfig';

type RoomProps = {
  renderRoomView?: (props: { eventId?: string }) => React.ReactNode;
};

// 12px black seam between the chat column and the right-side pane (profile,
// media, thread, members). Every active right-side pane renders one of
// these locally; the parent flex row is painted the same void colour by
// `Room` so each pane's TL/BL rounded carve can expose the void cleanly.
function VoidGap() {
  return (
    <Box
      shrink="No"
      style={{
        width: toRem(VOJO_HORSESHOE_GAP_PX),
        backgroundColor: VOJO_HORSESHOE_VOID_COLOR,
      }}
    />
  );
}

export function Room({ renderRoomView }: RoomProps) {
  const { eventId } = useParams();
  const room = useRoom();
  const mx = useMatrixClient();
  const channelsMode = useChannelsMode();
  // Match the thread URL pattern via useMatch rather than reading
  // useParams: rootId lives in a CHILD route relative to <Room>, so
  // useParams here returns only the room-level segments. useMatch
  // walks the absolute URL and gives us all four segments back.
  const threadMatch = useMatch({ path: CHANNELS_THREAD_PATH, end: true });
  const matchedSpaceParam = threadMatch?.params.spaceIdOrAlias;
  const matchedRoomParam = threadMatch?.params.roomIdOrAlias;
  const matchedRootParam = threadMatch?.params.rootId;
  // Suppress the drawer in bridged rooms. Telegram puppets have no
  // m.thread on the bridge side; M7 will replace this gate with an
  // inline-quote indicator. Until then, keeping the drawer hidden
  // matches the «Reply in Thread» button gate in RoomTimeline.
  const showThreadDrawer =
    channelsMode &&
    !!matchedRootParam &&
    !!matchedSpaceParam &&
    !!matchedRoomParam &&
    !isBridgedRoom(room);
  let parentRoomPath: string | undefined;
  let decodedRootId: string | undefined;
  if (showThreadDrawer && matchedSpaceParam && matchedRoomParam && matchedRootParam) {
    try {
      parentRoomPath = getChannelsRoomPath(
        decodeURIComponent(matchedSpaceParam),
        decodeURIComponent(matchedRoomParam)
      );
    } catch {
      parentRoomPath = getChannelsRoomPath(matchedSpaceParam, matchedRoomParam);
    }
    try {
      decodedRootId = decodeURIComponent(matchedRootParam);
    } catch {
      decodedRootId = matchedRootParam;
    }
  }
  // Legacy `isPeopleDrawer` localStorage setting still drives the
  // space-lobby MembersDrawer (see `features/lobby/Lobby.tsx`). The
  // room view itself migrates to the transient atom-driven members
  // sheet, so the value is no longer read here.
  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
  const screenSize = useScreenSizeContext();
  const isMobile = screenSize === ScreenSize.Mobile;
  // Mobile drawer takes over the chat column instead of sitting beside
  // it — the timeline+composer stay mounted but visually hidden, so the
  // M5 nav-stack work picks up where M2 left off without re-routing.
  const drawerHidesChat = showThreadDrawer && isMobile;
  const powerLevels = usePowerLevels(room);
  const chat = useAtomValue(callChatAtom);
  // 1:1 rooms get peer-profile-sheet via avatar tap in the header instead of
  // the members drawer — see dm_1x1_redesign.md §8 P4 deliverable 9. The
  // value comes from the same `IsOneOnOneProvider` the header reads, so the
  // drawer-suppression and header chrome flip together when membership
  // changes (provider is reactive via `useIsOneOnOneRoom` upstream).
  const isOneOnOne = useIsOneOnOne();

  const unreadThreadingEnabled = useUnreadThreadingEnabled();

  // Profile horseshoe: when the right-side profile pane is open on
  // desktop/tablet, paint a 12px void seam between the chat column and
  // the profile, and carve rounded TL/BL corners on the profile pane.
  // The chat column itself keeps a straight right edge (no rounding) —
  // only the profile carves, so the void gap reads as one black bar
  // with a single rounded contour on the right side of the seam.
  // Mirrors the page-nav <-> chat split from PageRoot (commit 363bd9d)
  // minus the chat-side rounding. Mobile and the thread-drawer case
  // don't mount the side pane, so the wrap is gated on both.
  //
  // Void colour is painted on the parent flex row (covering everything
  // not painted by an opaque child) so the profile pane's TL/BL carves
  // expose void rather than the chat-panel-inner's Background from
  // PageRoot. The chat column applies an explicit Background bg so the
  // parent void can't bleed through any transparent slivers.
  const profileOpen = !!useAtomValue(userRoomProfileAtom);
  const membersSheetOpen = !!useAtomValue(roomMembersSheetAtom);
  const callView = room.isCallRoom();
  const showProfileHorseshoe = profileOpen && !isMobile && !showThreadDrawer;
  // The desktop/tablet media viewer is no longer a right-side pane — it's
  // a free-floating draggable window (`RoomViewMediaWindow`) portaled over
  // the app, so it doesn't carve a horseshoe seam or paint the parent void.
  // Thread drawer side pane on desktop. Mobile hides the chat column
  // entirely (`drawerHidesChat`) so the seam doesn't apply there.
  const showThreadHorseshoe = showThreadDrawer && !isMobile;
  // Members side pane — mirrors the profile / media seam. Gated on:
  // any non-1:1 room (group DM, group room, channel) on desktop, atom
  // open, no thread overlay, no call surface. The atom-driven model
  // replaces the legacy `settingsAtom.isPeopleDrawer` localStorage flag
  // in the room view — `LobbyHeader` keeps reading that flag for the
  // space lobby. Channels share the same members surface as group
  // rooms per design canon `stream-v2-dawn.jsx::ChannelDesktop`.
  const showMembersHorseshoe =
    !callView && !isOneOnOne && !showThreadDrawer && !isMobile && membersSheetOpen;
  // Mobile mounts the members horseshoe wrapper for every non-1:1,
  // non-call room — group DMs, group rooms, AND channels. 1:1 rooms
  // keep using `RoomViewProfilePanel`; the call surface routes member
  // discovery through Room Settings (see the user-icon button kept in
  // `RoomViewHeaderDm` for `callView`).
  const useMembersWrapper = !isOneOnOne && !callView;
  // True whenever any right-side pane is mounted. Drives the parent flex
  // row's void background and the chat column's explicit Background paint
  // — both prevent the chat-side surface from bleeding through the carved
  // TL/BL of whichever pane is open. The per-pane `<VoidGap />` decisions
  // remain local to each render site (a single `paintParentVoid` gate
  // would over-render when only members is open — there is no
  // profile/media slot to anchor it to).
  const paintParentVoid = showProfileHorseshoe || showThreadHorseshoe || showMembersHorseshoe;

  useKeyDown(
    window,
    useCallback(
      (evt) => {
        // Escape marks the room as read. Pre-M4 the receipt handler
        // blind-deleted ALL room unread on any own receipt, so the
        // drawer-open case had to be skipped to avoid wiping thread
        // unread the user hadn't seen. After the M4 receipt-handler
        // refactor (PUT-with-fresh, partitioned by thread_id) main and
        // thread unread are independent, so Escape on the channel main
        // surface marks main as read and leaves threads alone — works
        // unconditionally. Under the kill switch the handler reverts
        // to blind DELETE, so we keep the drawer-open guard there.
        if (isKeyHotkey('escape', evt) && (unreadThreadingEnabled || !showThreadDrawer)) {
          markAsRead(mx, room.roomId, hideActivity);
        }
      },
      [mx, room.roomId, hideActivity, showThreadDrawer, unreadThreadingEnabled]
    )
  );

  // Disable the atom-driven media viewer when the desktop thread
  // drawer is open — with the host context cleared, a timeline
  // image/video tap falls back to the legacy full-screen `<Overlay>`
  // modal instead of opening a floating window on top of the drawer
  // (the `<Overlay>` sits above every pane and still works). NB: an
  // already-open floating window is NOT torn down by this — the window
  // mounts for all non-mobile screens and stays visible over the
  // drawer; only NEW taps are routed to the legacy modal.
  // Mobile is unaffected: `drawerHidesChat` hides the chat column
  // entirely on mobile + thread, so there's no media to tap anyway.
  //
  // `useMemo` so the value object identity is stable across
  // unrelated re-renders — without it, every parent re-render
  // would spread a new `{ roomId }` literal through the context,
  // re-running every `useContext(MediaViewerHostContext)` consumer
  // (every image / video thumbnail in the timeline).
  const mediaHostValue = React.useMemo(
    () => (!isMobile && showThreadDrawer ? null : { roomId: room.roomId }),
    [isMobile, showThreadDrawer, room.roomId]
  );

  return (
    <PowerLevelsContextProvider value={powerLevels}>
      <ThreadDrawerOpenProvider value={showThreadDrawer}>
        <MediaViewerHostContext.Provider value={mediaHostValue}>
          <Box
            grow="Yes"
            style={paintParentVoid ? { backgroundColor: VOJO_HORSESHOE_VOID_COLOR } : undefined}
          >
            {callView && (screenSize === ScreenSize.Desktop || !chat) && (
              <Box
                grow="Yes"
                direction="Column"
                className={paintParentVoid ? ContainerColor({ variant: 'Background' }) : undefined}
                // No chat-column padding-top / bg: the silhouette inside
                // `MobileProfileHorseshoe` owns the safe-top inset and bg.
                // See the !callView twin block below for the rationale.
              >
                <MobileMediaViewerHorseshoe>
                  {/* `useMembersWrapper` is gated on `!callView`, so this
                    branch is always the profile wrapper — kept explicit
                    for parity with the `!callView` twin below. */}
                  <RoomViewProfilePanel header={<RoomViewHeader callView />}>
                    <Box grow="Yes">
                      <CallView />
                    </Box>
                  </RoomViewProfilePanel>
                </MobileMediaViewerHorseshoe>
              </Box>
            )}
            {!callView && !drawerHidesChat && (
              <Box
                grow="Yes"
                direction="Column"
                className={paintParentVoid ? ContainerColor({ variant: 'Background' }) : undefined}
                // No padding-top / bg on mobile chat column: whichever
                // mobile horseshoe wraps the chat (profile for 1:1,
                // members for group / channel) permanently sits at
                // body_top with its own `padding-top: var(--vojo-safe-
                // top)` keeping chat-header / panel content below the
                // status-bar icons. The silhouette's bg fades between
                // chat-surface tone (when closed) and sheet tone (when
                // fully open) as the user drags. Desktop branch still
                // works because `--vojo-safe-top` resolves to 0 on web.
              >
                <MobileMediaViewerHorseshoe>
                  {useMembersWrapper ? (
                    <RoomViewMembersPanel header={<RoomViewHeader />}>
                      <Box grow="Yes">
                        {renderRoomView?.({ eventId }) ?? <RoomView eventId={eventId} />}
                      </Box>
                    </RoomViewMembersPanel>
                  ) : (
                    <RoomViewProfilePanel header={<RoomViewHeader />}>
                      <Box grow="Yes">
                        {renderRoomView?.({ eventId }) ?? <RoomView eventId={eventId} />}
                      </Box>
                    </RoomViewProfilePanel>
                  )}
                </MobileMediaViewerHorseshoe>
              </Box>
            )}

            {/* Tablet / Desktop: profile or media renders as a third pane
            to the right of the chat. Mobile uses the top horseshoe
            (profile) and the bottom horseshoe (media), so we don't
            mount the side panes there. The 12px void gap sits between
            the chat column and whichever pane is open — both panes
            share the same gap geometry since they're mutually
            exclusive via the open hooks. */}
            {!isMobile && !showThreadDrawer && (
              <>
                {showProfileHorseshoe && <VoidGap />}
                <RoomViewProfileSidePanel />
              </>
            )}
            {/* Desktop / tablet media viewer — a free-floating draggable
            window portaled over the app (replaces the old right-side
            pane). It self-gates on `mediaViewerAtom`, so mount it for
            every non-mobile screen — unconditionally on the thread
            drawer too, so a window opened before the drawer stays
            visible over it (opening NEW media during the drawer is
            still routed to the legacy modal — see `mediaHostValue`). */}
            {!isMobile && <RoomViewMediaWindow />}

            {callView && chat && (
              <>
                {screenSize === ScreenSize.Desktop && (
                  <Line variant="Background" direction="Vertical" size="300" />
                )}
                <CallChatView />
              </>
            )}
            {/* Members side pane — atom-driven (transient), opens via
              tap on avatar+title in the chat header (see
              `RoomViewHeaderDm`). Hidden when thread drawer is open —
              three simultaneous side panes don't fit on anything
              narrower than ultrawide. */}
            {showMembersHorseshoe && (
              <>
                <VoidGap />
                <RoomViewMembersSidePanel key={room.roomId} />
              </>
            )}
            {showThreadDrawer && decodedRootId && parentRoomPath && (
              <>
                {showThreadHorseshoe && <VoidGap />}
                <ThreadDrawer
                  key={`${room.roomId}/${decodedRootId}`}
                  room={room}
                  rootId={decodedRootId}
                  parentRoomPath={parentRoomPath}
                  variant={isMobile ? 'mobile' : 'desktop'}
                />
              </>
            )}
          </Box>
        </MediaViewerHostContext.Provider>
      </ThreadDrawerOpenProvider>
    </PowerLevelsContextProvider>
  );
}
