import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAtomValue } from 'jotai';
import { Box, Button, Icon, Icons, Text, color, toRem } from 'folds';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useNavigate } from 'react-router-dom';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { factoryRoomIdByActivity } from '../../../utils/sort';
import { NavCategory, NavEmptyCenter, NavEmptyLayout } from '../../../components/nav';
import { getDirectCreatePath, getDirectRoomPath } from '../../pathUtils';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
import { VirtualTile } from '../../../components/virtualizer';
import { DirectInviteRow, DmStreamRow } from '../../../features/room-nav';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { useDirectRooms } from './useDirectRooms';
import { useDirectInvites, DirectInviteEntry } from './useDirectInvites';
import { PageNav, PageNavContent } from '../../../components/page';
import { DIRECT_PATH } from '../../paths';
import {
  getRoomNotificationMode,
  useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences';
import { StreamHeader } from '../../../components/stream-header';
import { DirectSelfRow } from './DirectSelfRow';
import { MobileSettingsHorseshoe } from '../../../features/settings';
import { useMobilePagerPane } from '../../../components/mobile-tabs-pager/MobilePagerPaneContext';

type ListItem =
  | { kind: 'invite'; entry: DirectInviteEntry }
  | { kind: 'spam-toggle'; spamCount: number; expanded: boolean }
  | { kind: 'spam-invite'; entry: DirectInviteEntry }
  | { kind: 'direct'; roomId: string };

function DirectEmpty() {
  const { t } = useTranslation();
  const navigate = useNavigate();

  return (
    <NavEmptyCenter>
      <NavEmptyLayout
        icon={<Icon size="600" src={Icons.Mention} />}
        title={
          <Text size="H5" align="Center">
            {t('Direct.no_direct_messages')}
          </Text>
        }
        content={
          <Text size="T300" align="Center">
            {t('Direct.no_direct_messages_desc')}
          </Text>
        }
        options={
          <Button variant="Primary" size="300" onClick={() => navigate(getDirectCreatePath())}>
            <Text size="B300" truncate>
              {t('Direct.start_first_chat')}
            </Text>
          </Button>
        }
      />
    </NavEmptyCenter>
  );
}

type SpamToggleRowProps = {
  spamCount: number;
  expanded: boolean;
  onToggle: () => void;
};
function SpamToggleRow({ spamCount, expanded, onToggle }: SpamToggleRowProps) {
  const { t } = useTranslation();
  return (
    <Box
      as="button"
      type="button"
      onClick={onToggle}
      alignItems="Center"
      gap="200"
      aria-expanded={expanded}
      style={{
        appearance: 'none',
        WebkitAppearance: 'none',
        border: 'none',
        background: 'transparent',
        cursor: 'pointer',
        padding: `${toRem(8)} ${toRem(12)}`,
        width: '100%',
        color: color.Surface.OnContainer,
        opacity: 0.7,
        textAlign: 'left',
        font: 'inherit',
      }}
    >
      <Icon size="100" src={expanded ? Icons.ChevronTop : Icons.ChevronBottom} />
      <Text as="span" size="T200" style={{ fontWeight: 500 }}>
        {expanded
          ? t('Direct.invite_hide_spam')
          : t('Direct.invite_show_spam', { count: spamCount })}
      </Text>
    </Box>
  );
}

export function Direct() {
  const mx = useMatrixClient();
  // The mobile tab pager keeps Direct mounted while the URL points at a
  // sibling tab (/channels/, /bots/). Gate the persisted-path write on the
  // /direct/ prefix so the atom isn't poisoned with a foreign pathname.
  useNavToActivePathMapper('direct', DIRECT_PATH);
  const scrollRef = useRef<HTMLDivElement>(null);
  const directs = useDirectRooms();
  const invites = useDirectInvites();
  const notificationPreferences = useRoomsNotificationPreferencesContext();
  const [spamExpanded, setSpamExpanded] = useState(false);
  // roomToUnreadAtom only changes on read/unread transitions and ignores own
  // events — covers incoming notifying messages but not own sends or muted
  // incoming. Kept as a subscribe-only re-render trigger.
  useAtomValue(roomToUnreadAtom);

  // Activity tick: bump on every live timeline event in any DM room so list
  // order, preview text and time labels refresh on own sends, muted incoming,
  // reactions — anything roomToUnread misses. Uses the 'Room.timeline'
  // literal (= RoomEvent.Timeline) to avoid named imports from matrix-js-sdk
  // that the project's moduleResolution flags as TS2614 (see
  // docs/known-tech-debt-lint/). `directs` is read via a ref so the listener
  // doesn't re-bind on every set-identity change (heavy sessions can flip
  // the set 5-10× during sync).
  const [, setActivityTick] = useState(0);
  const directsRef = useRef(directs);
  directsRef.current = directs;
  useEffect(() => {
    const handleTimeline = (
      _ev: unknown,
      room: { roomId: string } | undefined,
      _start: unknown,
      _removed: unknown,
      data: { liveEvent?: boolean } | undefined
    ) => {
      if (!room || !data?.liveEvent) return;
      if (!directsRef.current.includes(room.roomId)) return;
      setActivityTick((n) => n + 1);
    };
    const emitter = mx as unknown as {
      on: (e: string, h: typeof handleTimeline) => void;
      removeListener: (e: string, h: typeof handleTimeline) => void;
    };
    emitter.on('Room.timeline', handleTimeline);
    return () => {
      emitter.removeListener('Room.timeline', handleTimeline);
    };
  }, [mx]);

  const selectedRoomId = useSelectedRoom();

  const items = useMemo<ListItem[]>(() => {
    const list: ListItem[] = [];
    const cleanInvites = invites.filter((i) => !i.isSpam);
    const spamInvites = invites.filter((i) => i.isSpam);
    cleanInvites.forEach((entry) => list.push({ kind: 'invite', entry }));
    if (spamInvites.length > 0) {
      list.push({
        kind: 'spam-toggle',
        spamCount: spamInvites.length,
        expanded: spamExpanded,
      });
      if (spamExpanded) {
        spamInvites.forEach((entry) => list.push({ kind: 'spam-invite', entry }));
      }
    }
    const sortedDirects = Array.from(directs).sort(factoryRoomIdByActivity(mx));
    sortedDirects.forEach((roomId) => list.push({ kind: 'direct', roomId }));
    return list;
  }, [invites, directs, spamExpanded, mx]);

  const noRoomToDisplay = items.length === 0;

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => scrollRef.current,
    // Per-kind estimates so the initial scroll height is close to actual
    // before measureElement self-corrects: invite cards are ~140px (header +
    // sub-line + buttons + optional topic), spam toggle is ~32px, DM rows
    // ~68px (avatar 48px + 2-line title block + padding). Keeps first paint
    // stable when the panel opens with invites.
    estimateSize: (index) => {
      const item = items[index];
      if (!item) return 68;
      if (item.kind === 'invite' || item.kind === 'spam-invite') return 140;
      if (item.kind === 'spam-toggle') return 32;
      return 68;
    },
    // Stable per-item identity so the measurement cache survives item-kind
    // shifts at the same index. Without this TanStack falls back to index,
    // and a DM row at index 0 inheriting an invite card's measured height
    // (or vice versa) flashes a wrong size for one frame on every list
    // mutation that adds/removes an invite at the top.
    getItemKey: (index) => {
      const item = items[index];
      if (!item) return index;
      if (item.kind === 'invite') return `invite:${item.entry.roomId}`;
      if (item.kind === 'spam-invite') return `spam-invite:${item.entry.roomId}`;
      if (item.kind === 'spam-toggle') return 'spam-toggle';
      return `direct:${item.roomId}`;
    },
    overscan: 10,
  });

  // In pager mode the PageNav surface bg would cover the pager's
  // static header tabs (which sit behind the swipe strip in DOM order
  // so the chats curtain can rise OVER them like a real blind). Skip
  // the surface paint here in pager mode — the static header behind
  // owns the visible safe-top + tabsRow tone. See
  // `mobile-tabs-pager/style.css.ts::pagerStaticHeader` for the
  // overlay contract.
  const inPagerMode = useMobilePagerPane() !== null;

  return (
    <PageNav resizable surface={inPagerMode ? undefined : 'surfaceVariant'}>
      {/* MobileSettingsHorseshoe wraps the full DM column on mobile so the
          Settings sheet can carve into the bottom of this pane. On non-mobile
          it's a pass-through. */}
      <MobileSettingsHorseshoe>
        <StreamHeader scrollRef={scrollRef} bottomPinned={<DirectSelfRow />} pinKey="direct">
          {noRoomToDisplay ? (
            <DirectEmpty />
          ) : (
            <PageNavContent scrollRef={scrollRef}>
              <Box direction="Column" gap="300">
                <NavCategory>
                  <div style={{ position: 'relative', height: virtualizer.getTotalSize() }}>
                    {virtualizer.getVirtualItems().map((vItem) => {
                      const item = items[vItem.index];
                      if (!item) return null;

                      if (item.kind === 'invite' || item.kind === 'spam-invite') {
                        const { entry } = item;
                        const selected = selectedRoomId === entry.roomId;
                        return (
                          <VirtualTile
                            virtualItem={vItem}
                            key={`invite-${entry.roomId}`}
                            ref={virtualizer.measureElement}
                          >
                            <DirectInviteRow
                              room={entry.room}
                              selected={selected}
                              isSpam={item.kind === 'spam-invite'}
                            />
                          </VirtualTile>
                        );
                      }

                      if (item.kind === 'spam-toggle') {
                        return (
                          <VirtualTile
                            virtualItem={vItem}
                            key="spam-toggle"
                            ref={virtualizer.measureElement}
                          >
                            <SpamToggleRow
                              spamCount={item.spamCount}
                              expanded={item.expanded}
                              onToggle={() => setSpamExpanded((v) => !v)}
                            />
                          </VirtualTile>
                        );
                      }

                      const { roomId } = item;
                      const room = mx.getRoom(roomId);
                      if (!room) return null;
                      const selected = selectedRoomId === roomId;
                      return (
                        <VirtualTile
                          virtualItem={vItem}
                          key={`direct-${roomId}`}
                          ref={virtualizer.measureElement}
                        >
                          <DmStreamRow
                            room={room}
                            selected={selected}
                            linkPath={getDirectRoomPath(getCanonicalAliasOrRoomId(mx, roomId))}
                            notificationMode={getRoomNotificationMode(
                              notificationPreferences,
                              room.roomId
                            )}
                          />
                        </VirtualTile>
                      );
                    })}
                  </div>
                </NavCategory>
              </Box>
            </PageNavContent>
          )}
        </StreamHeader>
      </MobileSettingsHorseshoe>
    </PageNav>
  );
}
