import React, {
  ChangeEvent,
  MouseEventHandler,
  forwardRef,
  useMemo,
  useRef,
  useState,
} from 'react';
import { useNavigate } from 'react-router';
import {
  Avatar,
  Box,
  Button,
  Icon,
  IconButton,
  Icons,
  Input,
  Menu,
  MenuItem,
  PopOut,
  RectCords,
  Text,
  config,
  toRem,
} from 'folds';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useAtom, useAtomValue } from 'jotai';
import { selectAtom } from 'jotai/utils';
import FocusTrap from 'focus-trap-react';
import {
  factoryRoomIdByActivity,
  factoryRoomIdByAtoZ,
  factoryRoomIdByUnread,
} from '../../../utils/sort';
import {
  NavButton,
  NavCategory,
  NavCategoryHeader,
  NavEmptyCenter,
  NavEmptyLayout,
  NavItem,
  NavItemContent,
  NavLink,
} from '../../../components/nav';
import {
  encodeSearchParamValueArray,
  getExplorePath,
  getHomeCreatePath,
  getHomeRoomPath,
  getHomeSearchPath,
  withSearchParam,
} from '../../pathUtils';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import {
  useHomeCreateSelected,
  useHomeSearchSelected,
} from '../../../hooks/router/useHomeSelected';
import { useHomeRooms } from './useHomeRooms';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page';
import { useRoomsUnread } from '../../../state/hooks/unread';
import { markAsRead } from '../../../utils/notifications';
import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import {
  getRoomNotificationMode,
  useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { JoinAddressPrompt } from '../../../components/join-address-prompt';
import { _RoomSearchParams } from '../../paths';
import { getLocalRoomNamesContent } from '../../../hooks/useRoomMeta';

type HomeMenuProps = {
  requestClose: () => void;
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
  const orphanRooms = useHomeRooms();
  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
  const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
  const mx = useMatrixClient();

  const handleMarkAsRead = () => {
    if (!unread) return;
    orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
    requestClose();
  };

  return (
    <Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
      <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
        <MenuItem
          onClick={handleMarkAsRead}
          size="300"
          after={<Icon size="100" src={Icons.CheckTwice} />}
          radii="300"
          aria-disabled={!unread}
        >
          <Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
            Mark as Read
          </Text>
        </MenuItem>
      </Box>
    </Menu>
  );
});

function HomeHeader() {
  const [menuAnchor, setMenuAnchor] = useState<RectCords>();

  const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
    const cords = evt.currentTarget.getBoundingClientRect();
    setMenuAnchor((currentState) => {
      if (currentState) return undefined;
      return cords;
    });
  };

  return (
    <>
      <PageNavHeader>
        <Box alignItems="Center" grow="Yes" gap="300">
          <Box grow="Yes">
            <Text as="h2" size="H4" truncate>
              Home
            </Text>
          </Box>
          <Box>
            <IconButton
              aria-expanded={!!menuAnchor}
              aria-haspopup="menu"
              variant="Background"
              onClick={handleOpenMenu}
              aria-label="Home options"
            >
              <Icon src={Icons.VerticalDots} size="200" />
            </IconButton>
          </Box>
        </Box>
      </PageNavHeader>
      <PopOut
        anchor={menuAnchor}
        position="Bottom"
        align="End"
        offset={6}
        content={
          <FocusTrap
            focusTrapOptions={{
              initialFocus: false,
              returnFocusOnDeactivate: false,
              onDeactivate: () => setMenuAnchor(undefined),
              clickOutsideDeactivates: true,
              isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
              isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
              escapeDeactivates: stopPropagation,
            }}
          >
            <HomeMenu requestClose={() => setMenuAnchor(undefined)} />
          </FocusTrap>
        }
      />
    </>
  );
}

function HomeEmpty() {
  const navigate = useNavigate();

  return (
    <NavEmptyCenter>
      <NavEmptyLayout
        icon={<Icon size="600" src={Icons.Hash} />}
        title={
          <Text size="H5" align="Center">
            No Rooms
          </Text>
        }
        content={
          <Text size="T300" align="Center">
            You do not have any rooms yet.
          </Text>
        }
        options={
          <>
            <Button onClick={() => navigate(getHomeCreatePath())} variant="Secondary" size="300">
              <Text size="B300" truncate>
                Create Room
              </Text>
            </Button>
            <Button
              onClick={() => navigate(getExplorePath())}
              variant="Secondary"
              fill="Soft"
              size="300"
            >
              <Text size="B300" truncate>
                Explore Community Rooms
              </Text>
            </Button>
          </>
        }
      />
    </NavEmptyCenter>
  );
}

const DEFAULT_CATEGORY_ID = makeNavCategoryId('home', 'room');
const FAVORITES_CATEGORY_ID = makeNavCategoryId('home', 'favorite');
const LOW_PRIORITY_CATEGORY_ID = makeNavCategoryId('home', 'lowpriority');
export function Home() {
  const mx = useMatrixClient();
  useNavToActivePathMapper('home');
  const scrollRef = useRef<HTMLDivElement>(null);
  const rooms = useHomeRooms();
  const notificationPreferences = useRoomsNotificationPreferencesContext();
  const [filterQuery, setFilterQuery] = useState<string>('');
  // Perf-3: only re-render when the set of rooms WITH unread changes, not on count updates
  const roomsWithUnreadSet = useAtomValue(
    useMemo(
      () =>
        selectAtom(
          roomToUnreadAtom,
          (rtu) => {
            const s = new Set<string>();
            for (const id of rtu.keys()) s.add(id);
            return s;
          },
          (a, b) => {
            if (a.size !== b.size) return false;
            for (const id of a) if (!b.has(id)) return false;
            return true;
          },
        ),
      [],
    ),
  );
  const navigate = useNavigate();

  const selectedRoomId = useSelectedRoom();
  const createRoomSelected = useHomeCreateSelected();
  const searchSelected = useHomeSearchSelected();
  const noRoomToDisplay = rooms.length === 0;
  const [closedCategories, setClosedCategories] = useAtom(useClosedNavCategoriesAtom());
  const [homeRoomSort, setHomeRoomSort] = useSetting(settingsAtom, 'homeRoomSort');
  const roomToUnread = useAtomValue(roomToUnreadAtom);
  const [sortMenuAnchor, setSortMenuAnchor] = useState<RectCords>();

  const { favoriteRooms, lowPriorityRooms, otherRooms } = useMemo(() => {
    const favs: string[] = [];
    const low: string[] = [];
    const others: string[] = [];
    rooms.forEach((rId) => {
      const room = mx.getRoom(rId);
      if (room?.tags?.['m.favourite']) {
        favs.push(rId);
      } else if (room?.tags?.['m.lowpriority']) {
        low.push(rId);
      } else {
        others.push(rId);
      }
    });
    return { favoriteRooms: favs, lowPriorityRooms: low, otherRooms: others };
  }, [mx, rooms]);

  const sortedFavoriteRooms = useMemo(() => {
    const isClosed = closedCategories.has(FAVORITES_CATEGORY_ID);
    const items = Array.from(favoriteRooms).sort(
      isClosed ? factoryRoomIdByActivity(mx) : factoryRoomIdByAtoZ(mx),
    );
    if (isClosed) {
      return items.filter((rId) => roomsWithUnreadSet.has(rId) || rId === selectedRoomId);
    }
    return items;
  }, [mx, favoriteRooms, closedCategories, roomsWithUnreadSet, selectedRoomId]);

  const filteredFavoriteRooms = useMemo(() => {
    if (!filterQuery.trim()) return sortedFavoriteRooms;
    const query = filterQuery.toLowerCase();
    const localNames = getLocalRoomNamesContent(mx);
    return sortedFavoriteRooms.filter((rId) => {
      const localName = localNames.rooms[rId];
      const matrixName = mx.getRoom(rId)?.name ?? '';
      return (localName ?? matrixName).toLowerCase().includes(query);
    });
  }, [mx, sortedFavoriteRooms, filterQuery]);

  const sortedLowPriorityRooms = useMemo(() => {
    const isClosed = closedCategories.has(LOW_PRIORITY_CATEGORY_ID);
    const items = Array.from(lowPriorityRooms).sort(
      isClosed ? factoryRoomIdByActivity(mx) : factoryRoomIdByAtoZ(mx),
    );
    if (isClosed) {
      return items.filter((rId) => roomsWithUnreadSet.has(rId) || rId === selectedRoomId);
    }
    return items;
  }, [mx, lowPriorityRooms, closedCategories, roomsWithUnreadSet, selectedRoomId]);

  const filteredLowPriorityRooms = useMemo(() => {
    if (!filterQuery.trim()) return sortedLowPriorityRooms;
    const query = filterQuery.toLowerCase();
    const localNames = getLocalRoomNamesContent(mx);
    return sortedLowPriorityRooms.filter((rId) => {
      const localName = localNames.rooms[rId];
      const matrixName = mx.getRoom(rId)?.name ?? '';
      return (localName ?? matrixName).toLowerCase().includes(query);
    });
  }, [mx, sortedLowPriorityRooms, filterQuery]);

  const sortedRooms = useMemo(() => {
    const isClosed = closedCategories.has(DEFAULT_CATEGORY_ID);
    let comparator: (a: string, b: string) => number;
    if (isClosed) {
      comparator = factoryRoomIdByActivity(mx);
    } else if (homeRoomSort === 'alpha') {
      comparator = factoryRoomIdByAtoZ(mx);
    } else if (homeRoomSort === 'unread') {
      comparator = factoryRoomIdByUnread(roomToUnread, mx);
    } else {
      comparator = factoryRoomIdByActivity(mx);
    }
    const items = Array.from(otherRooms).sort(comparator);
    if (isClosed) {
      return items.filter((rId) => roomsWithUnreadSet.has(rId) || rId === selectedRoomId);
    }
    return items;
  }, [
    mx,
    otherRooms,
    closedCategories,
    roomsWithUnreadSet,
    selectedRoomId,
    homeRoomSort,
    roomToUnread,
  ]);

  const filteredRooms = useMemo(() => {
    if (!filterQuery.trim()) return sortedRooms;
    const query = filterQuery.toLowerCase();
    const localNames = getLocalRoomNamesContent(mx);
    return sortedRooms.filter((rId) => {
      const localName = localNames.rooms[rId];
      const matrixName = mx.getRoom(rId)?.name ?? '';
      return (localName ?? matrixName).toLowerCase().includes(query);
    });
  }, [mx, sortedRooms, filterQuery]);

  const favVirtualizer = useVirtualizer({
    count: filteredFavoriteRooms.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => 38,
    overscan: 10,
  });

  const virtualizer = useVirtualizer({
    count: filteredRooms.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => 38,
    overscan: 10,
  });

  const lowVirtualizer = useVirtualizer({
    count: filteredLowPriorityRooms.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => 38,
    overscan: 10,
  });

  const handleCategoryClick = useCategoryHandler(setClosedCategories, (categoryId) =>
    closedCategories.has(categoryId),
  );

  return (
    <PageNav>
      <HomeHeader />
      {noRoomToDisplay ? (
        <HomeEmpty />
      ) : (
        <PageNavContent scrollRef={scrollRef}>
          <Box direction="Column" gap="300">
            <NavCategory>
              <NavItem variant="Background" radii="400" aria-selected={createRoomSelected}>
                <NavButton onClick={() => navigate(getHomeCreatePath())}>
                  <NavItemContent>
                    <Box as="span" grow="Yes" alignItems="Center" gap="200">
                      <Avatar size="200" radii="400">
                        <Icon src={Icons.Plus} size="100" />
                      </Avatar>
                      <Box as="span" grow="Yes">
                        <Text as="span" size="Inherit" truncate>
                          Create Room
                        </Text>
                      </Box>
                    </Box>
                  </NavItemContent>
                </NavButton>
              </NavItem>
              <UseStateProvider initial={false}>
                {(open, setOpen) => (
                  <>
                    <NavItem variant="Background" radii="400">
                      <NavButton onClick={() => setOpen(true)}>
                        <NavItemContent>
                          <Box as="span" grow="Yes" alignItems="Center" gap="200">
                            <Avatar size="200" radii="400">
                              <Icon src={Icons.Link} size="100" />
                            </Avatar>
                            <Box as="span" grow="Yes">
                              <Text as="span" size="Inherit" truncate>
                                Join with Address
                              </Text>
                            </Box>
                          </Box>
                        </NavItemContent>
                      </NavButton>
                    </NavItem>
                    {open && (
                      <JoinAddressPrompt
                        onCancel={() => setOpen(false)}
                        onOpen={(roomIdOrAlias, viaServers, eventId) => {
                          setOpen(false);
                          const path = getHomeRoomPath(roomIdOrAlias, eventId);
                          navigate(
                            viaServers
                              ? withSearchParam<_RoomSearchParams>(path, {
                                  viaServers: encodeSearchParamValueArray(viaServers),
                                })
                              : path,
                          );
                        }}
                      />
                    )}
                  </>
                )}
              </UseStateProvider>
              <NavItem variant="Background" radii="400" aria-selected={searchSelected}>
                <NavLink to={getHomeSearchPath()}>
                  <NavItemContent>
                    <Box as="span" grow="Yes" alignItems="Center" gap="200">
                      <Avatar size="200" radii="400">
                        <Icon src={Icons.Search} size="100" filled={searchSelected} />
                      </Avatar>
                      <Box as="span" grow="Yes">
                        <Text as="span" size="Inherit" truncate>
                          Message Search
                        </Text>
                      </Box>
                    </Box>
                  </NavItemContent>
                </NavLink>
              </NavItem>
            </NavCategory>
            <NavCategory>
              <Box
                grow="Yes"
                style={{ padding: `0 ${config.space.S200}`, paddingBottom: config.space.S100 }}
                direction="Column"
                gap="100"
              >
                <Input
                  value={filterQuery}
                  onChange={(e: ChangeEvent<HTMLInputElement>) => setFilterQuery(e.target.value)}
                  placeholder="Filter rooms…"
                  variant="Surface"
                  size="400"
                  radii="400"
                  before={<Icon size="50" src={Icons.Search} />}
                  after={
                    filterQuery ? (
                      <IconButton
                        onClick={() => setFilterQuery('')}
                        size="400"
                        radii="Pill"
                        variant="Background"
                        fill="None"
                        aria-label="Clear filter"
                      >
                        <Icon size="50" src={Icons.Cross} />
                      </IconButton>
                    ) : undefined
                  }
                />
              </Box>
            </NavCategory>
            {favoriteRooms.length > 0 && (
              <NavCategory>
                <NavCategoryHeader>
                  <RoomNavCategoryButton
                    closed={closedCategories.has(FAVORITES_CATEGORY_ID)}
                    data-category-id={FAVORITES_CATEGORY_ID}
                    onClick={handleCategoryClick}
                  >
                    Favorites
                  </RoomNavCategoryButton>
                </NavCategoryHeader>
                <div style={{ position: 'relative', height: favVirtualizer.getTotalSize() }}>
                  {favVirtualizer.getVirtualItems().map((vItem) => {
                    const roomId = filteredFavoriteRooms[vItem.index];
                    const room = mx.getRoom(roomId);
                    if (!room) return null;
                    return (
                      <VirtualTile
                        virtualItem={vItem}
                        key={roomId}
                        ref={favVirtualizer.measureElement}
                      >
                        <RoomNavItem
                          room={room}
                          selected={selectedRoomId === roomId}
                          linkPath={getHomeRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
                          notificationMode={getRoomNotificationMode(
                            notificationPreferences,
                            room.roomId,
                          )}
                        />
                      </VirtualTile>
                    );
                  })}
                </div>
              </NavCategory>
            )}
            <NavCategory>
              <NavCategoryHeader>
                <RoomNavCategoryButton
                  closed={closedCategories.has(DEFAULT_CATEGORY_ID)}
                  data-category-id={DEFAULT_CATEGORY_ID}
                  onClick={handleCategoryClick}
                >
                  Rooms
                </RoomNavCategoryButton>
                {!closedCategories.has(DEFAULT_CATEGORY_ID) && (
                  <>
                    <IconButton
                      aria-expanded={!!sortMenuAnchor}
                      aria-haspopup="menu"
                      variant="Background"
                      fill="None"
                      size="300"
                      radii="300"
                      onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
                        const cords = evt.currentTarget.getBoundingClientRect();
                        setSortMenuAnchor((current) => (current ? undefined : cords));
                      }}
                      aria-label="Sort rooms"
                    >
                      <Icon src={Icons.Sort} size="100" />
                    </IconButton>
                    <PopOut
                      anchor={sortMenuAnchor}
                      position="Bottom"
                      align="End"
                      offset={6}
                      content={
                        <FocusTrap
                          focusTrapOptions={{
                            initialFocus: false,
                            returnFocusOnDeactivate: false,
                            onDeactivate: () => setSortMenuAnchor(undefined),
                            clickOutsideDeactivates: true,
                            isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
                            isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
                            escapeDeactivates: stopPropagation,
                          }}
                        >
                          <Menu style={{ maxWidth: toRem(180), width: '100vw' }}>
                            <Box
                              direction="Column"
                              gap="100"
                              style={{ padding: config.space.S100 }}
                            >
                              <MenuItem
                                onClick={() => {
                                  setHomeRoomSort('recent');
                                  setSortMenuAnchor(undefined);
                                }}
                                size="300"
                                after={
                                  homeRoomSort === 'recent' ? (
                                    <Icon size="100" src={Icons.Check} />
                                  ) : undefined
                                }
                                radii="300"
                              >
                                <Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
                                  Recent Activity
                                </Text>
                              </MenuItem>
                              <MenuItem
                                onClick={() => {
                                  setHomeRoomSort('alpha');
                                  setSortMenuAnchor(undefined);
                                }}
                                size="300"
                                after={
                                  homeRoomSort === 'alpha' ? (
                                    <Icon size="100" src={Icons.Check} />
                                  ) : undefined
                                }
                                radii="300"
                              >
                                <Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
                                  A → Z
                                </Text>
                              </MenuItem>
                              <MenuItem
                                onClick={() => {
                                  setHomeRoomSort('unread');
                                  setSortMenuAnchor(undefined);
                                }}
                                size="300"
                                after={
                                  homeRoomSort === 'unread' ? (
                                    <Icon size="100" src={Icons.Check} />
                                  ) : undefined
                                }
                                radii="300"
                              >
                                <Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
                                  Unread First
                                </Text>
                              </MenuItem>
                            </Box>
                          </Menu>
                        </FocusTrap>
                      }
                    />
                  </>
                )}
              </NavCategoryHeader>
              <div
                style={{
                  position: 'relative',
                  height: virtualizer.getTotalSize(),
                }}
              >
                {virtualizer.getVirtualItems().map((vItem) => {
                  const roomId = filteredRooms[vItem.index];
                  const room = mx.getRoom(roomId);
                  if (!room) return null;
                  const selected = selectedRoomId === roomId;

                  return (
                    <VirtualTile virtualItem={vItem} key={roomId} ref={virtualizer.measureElement}>
                      <RoomNavItem
                        room={room}
                        selected={selected}
                        linkPath={getHomeRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
                        notificationMode={getRoomNotificationMode(
                          notificationPreferences,
                          room.roomId,
                        )}
                      />
                    </VirtualTile>
                  );
                })}
              </div>
            </NavCategory>
            {lowPriorityRooms.length > 0 && (
              <NavCategory>
                <NavCategoryHeader>
                  <RoomNavCategoryButton
                    closed={closedCategories.has(LOW_PRIORITY_CATEGORY_ID)}
                    data-category-id={LOW_PRIORITY_CATEGORY_ID}
                    onClick={handleCategoryClick}
                  >
                    Low Priority
                  </RoomNavCategoryButton>
                </NavCategoryHeader>
                <div style={{ position: 'relative', height: lowVirtualizer.getTotalSize() }}>
                  {lowVirtualizer.getVirtualItems().map((vItem) => {
                    const roomId = filteredLowPriorityRooms[vItem.index];
                    const room = mx.getRoom(roomId);
                    if (!room) return null;
                    return (
                      <VirtualTile
                        virtualItem={vItem}
                        key={roomId}
                        ref={lowVirtualizer.measureElement}
                      >
                        <RoomNavItem
                          room={room}
                          selected={selectedRoomId === roomId}
                          linkPath={getHomeRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
                          notificationMode={getRoomNotificationMode(
                            notificationPreferences,
                            room.roomId,
                          )}
                        />
                      </VirtualTile>
                    );
                  })}
                </div>
              </NavCategory>
            )}
          </Box>
        </PageNavContent>
      )}
    </PageNav>
  );
}
