import React, { useEffect, useMemo, useRef } from 'react';
import { useAtom, useAtomValue } from 'jotai';
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
import { NotificationCountType, Room, Thread } from 'matrix-js-sdk';
import { Avatar, Box, Button, Header, Icon, IconButton, Icons, Scroll, Text, config } from 'folds';
import classNames from 'classnames';
import { useVirtualizer } from '@tanstack/react-virtual';
import * as css from './ThreadsListPanel.css';
import { ContainerColor } from '../../../styles/ContainerColor.css';
import { VirtualTile } from '../../../components/virtualizer';
import { UserAvatar } from '../../../components/user-avatar';
import { StackedAvatar } from '../../../components/stacked-avatar';
import { UnreadBadge, UnreadBadgeCenter } from '../../../components/unread-badge';
import { useMemberAvatar } from '../../../hooks/useMemberAvatar';
import { trimReplyFromBody } from '../../../utils/room';
import { scaleSystemEmoji } from '../../../plugins/react-custom-html-parser';
import { threadNotificationsAtom } from '../../../state/threadNotifications';
import { getMutedThreads } from '../../../utils/threadNotifications';
import { nameInitials } from '../../../utils/common';
import {
  ThreadFilter,
  ThreadSort,
  ThreadSnapshot,
  filterThreads,
  sortThreads,
  isThreadFilter,
  isThreadSort,
} from '../../../utils/threadList';
import { useRoomThreads } from '../../../hooks/useRoomThreads';

// Persisted across panel opens (the panel unmounts on close). getOnInit reads
// localStorage synchronously so the chosen filter/sort apply on first render.
const threadFilterAtom = atomWithStorage<ThreadFilter>(
  'cinny_threads_filter_v1',
  'all',
  createJSONStorage(() => localStorage),
  { getOnInit: true },
);
const threadSortAtom = atomWithStorage<ThreadSort>(
  'cinny_threads_sort_v1',
  'recent',
  createJSONStorage(() => localStorage),
  { getOnInit: true },
);

const FILTER_OPTIONS: { value: ThreadFilter; label: string }[] = [
  { value: 'all', label: 'All' },
  { value: 'unread', label: 'Unread' },
  { value: 'participating', label: 'Participating' },
];
const SORT_OPTIONS: { value: ThreadSort; label: string }[] = [
  { value: 'recent', label: 'Recent' },
  { value: 'oldest', label: 'Oldest' },
];

const MAX_PARTICIPANTS = 5;

function formatTimeAgo(ts: number): string {
  const diff = Date.now() - ts;
  const minutes = Math.floor(diff / 60_000);
  if (minutes < 1) return 'just now';
  if (minutes < 60) return `${minutes}m ago`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  if (days === 1) return 'yesterday';
  if (days < 7) return `${days}d ago`;
  return new Date(ts).toLocaleDateString();
}

// Segmented button, mirroring the Bookmarks panel sort control for consistency.
function SegButton({
  label,
  active,
  onClick,
}: {
  label: string;
  active: boolean;
  onClick: () => void;
}) {
  return (
    <Button
      size="300"
      variant={active ? 'Primary' : 'Secondary'}
      fill={active ? 'Solid' : 'Soft'}
      radii="300"
      aria-pressed={active}
      onClick={onClick}
    >
      <Text size="B300">{label}</Text>
    </Button>
  );
}

function ParticipantAvatar({ room, userId }: { room: Room; userId: string }) {
  const { name, avatarUrl } = useMemberAvatar(room, userId);
  return (
    <StackedAvatar title={name} variant="SurfaceVariant" size="200" radii="Pill">
      <UserAvatar
        userId={userId}
        src={avatarUrl}
        alt={name}
        renderFallback={() => <Icon size="50" src={Icons.User} filled />}
      />
    </StackedAvatar>
  );
}

type ThreadRowProps = {
  room: Room;
  thread: Thread;
  unread: number;
  highlight: number;
  participants: string[];
  onOpen: (threadId: string) => void;
};
function ThreadRow({ room, thread, unread, highlight, participants, onOpen }: ThreadRowProps) {
  const rootEvent = thread.rootEvent;
  const rootSender = rootEvent?.getSender() ?? '';
  const { name: rootName, avatarUrl } = useMemberAvatar(room, rootSender);
  const bodyRaw =
    typeof rootEvent?.getContent().body === 'string' ? (rootEvent.getContent().body as string) : '';
  const snippet = bodyRaw ? scaleSystemEmoji(trimReplyFromBody(bodyRaw)) : '(no preview)';
  const count = thread.length;
  const lastTs = thread.replyToEvent?.getTs() ?? rootEvent?.getTs();
  const extra = participants.length - MAX_PARTICIPANTS;
  const replyLabel = `${count} ${count === 1 ? 'reply' : 'replies'}`;
  const ariaLabel = `Open thread by ${rootName}${unread > 0 ? ', unread' : ''}, ${replyLabel}${
    typeof lastTs === 'number' ? `, last reply ${formatTimeAgo(lastTs)}` : ''
  }`;

  return (
    <Box
      as="button"
      direction="Column"
      gap="100"
      className={css.ThreadRow}
      onClick={() => onOpen(thread.id)}
      aria-label={ariaLabel}
    >
      <Box alignItems="Center" gap="200">
        <Avatar size="200" radii="300">
          <UserAvatar
            userId={rootSender}
            src={avatarUrl}
            alt={rootName}
            renderFallback={() => <Text size="H6">{nameInitials(rootName)}</Text>}
          />
        </Avatar>
        <Text size="T200" truncate style={{ flexGrow: 1, fontWeight: config.fontWeight.W600 }}>
          {rootName}
        </Text>
        {unread > 0 && (
          <UnreadBadgeCenter>
            <UnreadBadge highlight={highlight > 0} count={unread} />
          </UnreadBadgeCenter>
        )}
      </Box>

      <Text
        size="T200"
        priority="400"
        style={{
          display: '-webkit-box',
          WebkitLineClamp: 2,
          WebkitBoxOrient: 'vertical',
          overflow: 'hidden',
          wordBreak: 'break-word',
        }}
      >
        {snippet}
      </Text>

      <Box alignItems="Center" gap="200">
        <Icon size="50" src={Icons.Thread} />
        <Text size="T200" priority="300" truncate style={{ flexGrow: 1 }}>
          {count} {count === 1 ? 'reply' : 'replies'}
          {typeof lastTs === 'number' ? ` · ${formatTimeAgo(lastTs)}` : ''}
        </Text>
        <Box shrink="No" alignItems="Center">
          {participants.slice(0, MAX_PARTICIPANTS).map((userId) => (
            <ParticipantAvatar key={userId} room={room} userId={userId} />
          ))}
          {extra > 0 && (
            <Text size="T200" priority="300" style={{ marginLeft: config.space.S100 }}>
              +{extra}
            </Text>
          )}
        </Box>
      </Box>
    </Box>
  );
}

export type ThreadsListPanelProps = {
  room: Room;
  onClose: () => void;
  onOpenThread: (threadId: string) => void;
};
export function ThreadsListPanel({ room, onClose, onOpenThread }: ThreadsListPanelProps) {
  const threads = useRoomThreads(room);
  const threadNotifications = useAtomValue(threadNotificationsAtom);
  const [storedFilter, setFilter] = useAtom(threadFilterAtom);
  const [storedSort, setSort] = useAtom(threadSortAtom);
  const filter: ThreadFilter = isThreadFilter(storedFilter) ? storedFilter : 'all';
  const sort: ThreadSort = isThreadSort(storedSort) ? storedSort : 'recent';

  // Escape closes the panel (parity with the app's other drawers).
  useEffect(() => {
    const handleKeyDown = (evt: KeyboardEvent) => {
      if (evt.key === 'Escape' && !evt.defaultPrevented) {
        evt.preventDefault();
        evt.stopPropagation();
        onClose();
      }
    };
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [onClose]);

  const { visible, unreadById, highlightById, participantsById, threadById } = useMemo(() => {
    const mutedSet = getMutedThreads(threadNotifications, room.roomId);
    const unreadMap = new Map<string, number>();
    const highlightMap = new Map<string, number>();
    const partsMap = new Map<string, string[]>();
    const byId = new Map<string, Thread>();
    const snaps: ThreadSnapshot[] = threads.map((t) => {
      byId.set(t.id, t);
      const muted = mutedSet.has(t.id);
      const unread = muted
        ? 0
        : (room.getThreadUnreadNotificationCount(t.id, NotificationCountType.Total) ?? 0);
      const highlight = muted
        ? 0
        : (room.getThreadUnreadNotificationCount(t.id, NotificationCountType.Highlight) ?? 0);
      unreadMap.set(t.id, unread);
      highlightMap.set(t.id, highlight);

      // Participant senders: root first, then timeline order, plus the last
      // replier (from the server bundle) so it's present even if not paginated
      // into the loaded timeline yet. Deduped, order preserved.
      const seen = new Set<string>();
      const parts: string[] = [];
      const push = (s?: string | null) => {
        if (s && !seen.has(s)) {
          seen.add(s);
          parts.push(s);
        }
      };
      push(t.rootEvent?.getSender());
      t.timeline.forEach((e) => push(e.getSender()));
      push(t.replyToEvent?.getSender());
      partsMap.set(t.id, parts);

      return {
        id: t.id,
        latestTs: t.replyToEvent?.getTs() ?? t.rootEvent?.getTs() ?? 0,
        unread,
        participated: t.hasCurrentUserParticipated,
      };
    });
    const visibleSnaps = sortThreads(filterThreads(snaps, filter), sort);
    return {
      visible: visibleSnaps,
      unreadById: unreadMap,
      highlightById: highlightMap,
      participantsById: partsMap,
      threadById: byId,
    };
  }, [threads, room, filter, sort, threadNotifications]);

  const scrollRef = useRef<HTMLDivElement>(null) as React.RefObject<HTMLDivElement>;
  const virtualizer = useVirtualizer({
    count: visible.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => 96,
    overscan: 8,
    getItemKey: (index) => visible[index].id,
  });

  const totalThreads = threads.length;
  const emptyMessage =
    totalThreads === 0
      ? 'No threads in this room yet. Start one from a message’s “Reply in Thread”.'
      : 'No threads match this filter.';

  return (
    <Box
      className={classNames(css.ThreadsListPanel, ContainerColor({ variant: 'Background' }))}
      shrink="No"
      direction="Column"
    >
      <Header className={css.ThreadsListHeader} variant="Background" size="600">
        <Box grow="Yes" alignItems="Center" gap="200">
          <Icon src={Icons.Thread} size="200" />
          <Box grow="Yes" direction="Column">
            <Text size="H4" truncate>
              Threads
            </Text>
            <Text size="T200" truncate style={{ opacity: 0.65 }}>
              {room.name}
            </Text>
          </Box>
          <IconButton size="300" radii="300" aria-label="Close threads" onClick={onClose}>
            <Icon src={Icons.Cross} />
          </IconButton>
        </Box>
      </Header>

      <Box className={css.ThreadsListToolbar} direction="Column" gap="200">
        <Box role="group" aria-label="Filter threads" gap="100" wrap="Wrap">
          {FILTER_OPTIONS.map((opt) => (
            <SegButton
              key={opt.value}
              label={opt.label}
              active={filter === opt.value}
              onClick={() => setFilter(opt.value)}
            />
          ))}
        </Box>
        <Box role="group" aria-label="Sort threads" gap="100">
          {SORT_OPTIONS.map((opt) => (
            <SegButton
              key={opt.value}
              label={opt.label}
              active={sort === opt.value}
              onClick={() => setSort(opt.value)}
            />
          ))}
        </Box>
      </Box>

      <Box grow="Yes" style={{ minHeight: 0 }}>
        <Scroll ref={scrollRef} variant="Background" size="300" visibility="Hover" hideTrack>
          {visible.length === 0 ? (
            <Box
              direction="Column"
              alignItems="Center"
              justifyContent="Center"
              gap="300"
              style={{ padding: config.space.S700, textAlign: 'center' }}
            >
              <Icon size="600" src={Icons.Thread} style={{ opacity: config.opacity.Disabled }} />
              <Text size="T300" priority="300" align="Center">
                {emptyMessage}
              </Text>
            </Box>
          ) : (
            <Box className={css.ThreadsListContent} direction="Column">
              <div
                style={{ position: 'relative', height: virtualizer.getTotalSize(), width: '100%' }}
              >
                {virtualizer.getVirtualItems().map((vItem) => {
                  const snap = visible[vItem.index];
                  const thread = threadById.get(snap.id);
                  if (!thread) return null;
                  return (
                    <VirtualTile
                      key={snap.id}
                      virtualItem={vItem}
                      ref={virtualizer.measureElement}
                      style={{ width: '100%', paddingBottom: config.space.S200 }}
                    >
                      <ThreadRow
                        room={room}
                        thread={thread}
                        unread={unreadById.get(snap.id) ?? 0}
                        highlight={highlightById.get(snap.id) ?? 0}
                        participants={participantsById.get(snap.id) ?? []}
                        onOpen={onOpenThread}
                      />
                    </VirtualTile>
                  );
                })}
              </div>
            </Box>
          )}
        </Scroll>
      </Box>
    </Box>
  );
}
