import React, { memo, useLayoutEffect, useRef } from 'react';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import { Room as MatrixRoom } from 'matrix-js-sdk';
import { decodeRouteParam } from '../../pages/pathUtils';
import { useParams } from 'react-router-dom';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { IsDirectRoomProvider, RoomProvider } from '../../hooks/useRoom';
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { mDirectAtom } from '../../state/mDirectList';
import { ROOM_VIEW_CACHE_LIMIT, roomViewCacheAtom, touchRoomViewCache } from '../../state/roomViewCache';
import { roomNavLoadingAtom } from '../../state/pendingRoomNavigation';
import { RoomFrame, ActiveRoomEffects } from './Room';
import { RoomVisibilityRefContext } from './RoomViewActiveContext';
import {
  clearRoomTimelineVisibilityGate,
  setRoomTimelineVisible,
} from '../../state/roomTimelineVisibilityGate';

const activeSlotStyle: React.CSSProperties = {
  flex: 1,
  minWidth: 0,
  minHeight: 0,
  width: '100%',
  display: 'flex',
  flexDirection: 'column',
  position: 'relative',
  zIndex: 1,
};

const inactiveSlotStyle: React.CSSProperties = {
  position: 'absolute',
  inset: 0,
  display: 'flex',
  flexDirection: 'column',
  // Scroll is saved/restored manually — hide without opacity so we don't stack
  // compositor layers over the active room (looked like an all-black chat pane).
  visibility: 'hidden',
  pointerEvents: 'none',
  zIndex: -1,
};

type CachedRoomSlotProps = {
  room: MatrixRoom;
  active: boolean;
  isDirect: boolean;
  eventId?: string;
};

/**
 * Warm switch = CSS visibility only. Do not pass a changing `active` into
 * RoomFrame/RoomTimeline — that re-renders every message and feels sluggish.
 */
const CachedRoomSlot = memo(function CachedRoomSlot({
  room,
  active,
  isDirect,
  eventId,
}: CachedRoomSlotProps) {
  const visibilityRef = useRef(active);
  visibilityRef.current = active;
  const slotRef = useRef<HTMLDivElement>(null);
  const savedScrollRef = useRef<{ top: number; atBottom: boolean } | null>(null);
  const setLoadingRoomId = useSetAtom(roomNavLoadingAtom);
  // After the first hide, the next show is a warm keep-alive switch.
  const warmNextActivateRef = useRef(false);

  useLayoutEffect(() => {
    setRoomTimelineVisible(room.roomId, active);
    return () => clearRoomTimelineVisibilityGate(room.roomId);
  }, [active, room.roomId]);

  // Save/restore timeline scroll so warm switches don't jump.
  useLayoutEffect(() => {
    const scrollEl = slotRef.current?.querySelector(
      '[data-room-timeline-scroll]'
    ) as HTMLElement | null;
    if (!scrollEl) return undefined;

    if (!active) {
      const distanceFromBottom =
        scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight;
      savedScrollRef.current = {
        top: scrollEl.scrollTop,
        atBottom: distanceFromBottom <= 50,
      };
      return undefined;
    }

    const saved = savedScrollRef.current;
    if (!saved) return undefined;

    const restore = () => {
      if (saved.atBottom) {
        scrollEl.scrollTop = scrollEl.scrollHeight;
      } else {
        scrollEl.scrollTop = saved.top;
      }
    };

    restore();
    // Composer remounts on rAF and changes timeline height — re-apply after that.
    const raf1 = window.requestAnimationFrame(() => {
      restore();
      const raf2 = window.requestAnimationFrame(() => {
        restore();
        window.setTimeout(restore, 50);
      });
      void raf2;
    });
    return () => window.cancelAnimationFrame(raf1);
  }, [active]);

  useLayoutEffect(() => {
    if (!active) {
      warmNextActivateRef.current = true;
      return undefined;
    }
    if (!warmNextActivateRef.current) return undefined;
    // Warm path: timeline already mounted — clear the nav bar after a short flash.
    const t = window.setTimeout(() => {
      setLoadingRoomId((id) => (id === room.roomId ? null : id));
    }, 200);
    return () => window.clearTimeout(t);
  }, [active, room.roomId, setLoadingRoomId]);

  return (
    <div
      ref={slotRef}
      style={active ? activeSlotStyle : inactiveSlotStyle}
      aria-hidden={!active}
      {...(!active ? ({ inert: '' } as React.HTMLAttributes<HTMLDivElement>) : undefined)}
    >
      <RoomVisibilityRefContext.Provider value={visibilityRef}>
        <RoomProvider value={room}>
          <IsDirectRoomProvider value={isDirect}>
            {active && <ActiveRoomEffects room={room} />}
            <RoomFrame room={room} eventId={eventId} active={active} />
          </IsDirectRoomProvider>
        </RoomProvider>
      </RoomVisibilityRefContext.Provider>
    </div>
  );
});

/**
 * Keeps the last N room views mounted (hidden when inactive) so switching back
 * is instant — no remount.
 *
 * Active room comes from the URL (`useSelectedRoom`) so this can live outside
 * the room route element and survive param changes.
 */
export function CachedRooms() {
  const mx = useMatrixClient();
  const selectedRoomId = useSelectedRoom();
  const mDirects = useAtomValue(mDirectAtom);
  const [cache, setCache] = useAtom(roomViewCacheAtom);
  const { eventId: rawEventId } = useParams();
  const eventId = selectedRoomId ? decodeRouteParam(rawEventId) : undefined;

  useLayoutEffect(() => {
    if (!selectedRoomId) return;
    setCache((prev) => touchRoomViewCache(prev, selectedRoomId, ROOM_VIEW_CACHE_LIMIT).next);
  }, [selectedRoomId, setCache]);

  const ids =
    selectedRoomId && !cache.includes(selectedRoomId)
      ? [selectedRoomId, ...cache.filter((id) => id !== selectedRoomId)].slice(
          0,
          ROOM_VIEW_CACHE_LIMIT
        )
      : cache;

  if (ids.length === 0) return null;

  return (
    <div
      style={{
        flex: 1,
        minWidth: 0,
        minHeight: 0,
        width: '100%',
        position: 'relative',
        display: 'flex',
        flexDirection: 'column',
      }}
    >
      {ids.map((roomId) => {
        const room = mx.getRoom(roomId);
        if (!room) return null;
        const active = roomId === selectedRoomId;

        return (
          <CachedRoomSlot
            key={roomId}
            room={room}
            active={active}
            isDirect={mDirects.has(roomId)}
            eventId={active ? eventId : undefined}
          />
        );
      })}
    </div>
  );
}
