import { acceptDm, type ConversationSummary, isConversationMuted } from '@chat-app/shared/chat';
import { extractErrorCode } from '@chat-app/shared/i18n';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink, Outlet, useParams } from 'react-router-dom';

import { ConversationRowMenu } from '../components/ConversationRowMenu';
import { CreateGroupDialog } from '../components/CreateGroupDialog';
import {
  AddUserIcon,
  ArchiveIcon,
  BellOffIcon,
  ChatBubbleIcon,
  PhoneIcon,
  SearchIcon,
  SpinnerIcon,
  UsersIcon,
} from '../components/icons';
import { UserBar } from '../components/UserBar';
import { useConversationsContext } from '../context/ConversationsContext';
import { useCallPresence } from '../lib/useCallPresence';
import { supabase } from '../lib/supabase';

export function ChatsPage() {
  const { conversations, loading, error, refresh, unread } = useConversationsContext();
  const { id: activeId } = useParams<{ id: string }>();
  const [createOpen, setCreateOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [showArchived, setShowArchived] = useState(false);

  const sorted = useMemo(() => {
    return [...conversations].sort((a, b) => {
      const ta = a.lastMessageAt ?? a.createdAt;
      const tb = b.lastMessageAt ?? b.createdAt;
      return tb.localeCompare(ta);
    });
  }, [conversations]);

  const queryFiltered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return sorted;
    return sorted.filter((c) => {
      const title = (c.type === 'dm' ? c.peer?.displayName : c.name) ?? '';
      const handle = c.type === 'dm' ? (c.peer?.username ?? '') : '';
      return title.toLowerCase().includes(q) || handle.toLowerCase().includes(q);
    });
  }, [sorted, query]);

  const activeItems = useMemo(() => queryFiltered.filter((c) => !c.archived), [queryFiltered]);
  const archivedItems = useMemo(() => queryFiltered.filter((c) => c.archived), [queryFiltered]);

  const archivedUnread = archivedItems.reduce((s, c) => s + (unread[c.id] ?? 0), 0);

  return (
    <div className="flex h-full">
      <ConversationList
        items={showArchived ? archivedItems : activeItems}
        loading={loading}
        error={error}
        activeId={activeId}
        unread={unread}
        query={query}
        onQueryChange={setQuery}
        onAccept={async (id) => {
          try {
            await acceptDm(supabase, id);
            await refresh();
          } catch (err: unknown) {
            const code = extractErrorCode(err);
            console.error('acceptDm failed', code ?? err);
          }
        }}
        onNewGroup={() => setCreateOpen(true)}
        showArchived={showArchived}
        onToggleArchived={() => setShowArchived((v) => !v)}
        archivedUnread={archivedUnread}
        activeCount={activeItems.length}
        archivedCount={archivedItems.length}
      />
      <div className="discord-chat-surface flex-1 border-l border-line bg-surface-3">
        <Outlet />
      </div>
      <CreateGroupDialog open={createOpen} onClose={() => setCreateOpen(false)} />
    </div>
  );
}

interface ConversationListProps {
  items: ConversationSummary[];
  loading: boolean;
  error: string | null;
  activeId: string | undefined;
  unread: Record<string, number>;
  query: string;
  onQueryChange: (q: string) => void;
  onAccept: (id: string) => void;
  onNewGroup: () => void;
  showArchived: boolean;
  onToggleArchived: () => void;
  archivedUnread: number;
  activeCount: number;
  archivedCount: number;
}

function ConversationList({
  items,
  loading,
  error,
  activeId,
  unread,
  query,
  onQueryChange,
  onAccept,
  onNewGroup,
  showArchived,
  onToggleArchived,
  archivedUnread,
  activeCount,
  archivedCount,
}: ConversationListProps) {
  const { t } = useTranslation(['app']);

  return (
    <aside
      aria-label="Conversations"
      className="discord-chat-panel flex h-full w-[312px] shrink-0 flex-col bg-surface-2"
    >
      <header className="px-4 pb-3 pt-4">
        <div className="flex items-center justify-between">
          <div className="min-w-0">
            <h2 className="font-display text-base font-semibold text-fg">
              {showArchived
                ? t('app:chats.archived_title', { defaultValue: 'Archiv' })
                : t('app:nav.chats')}
            </h2>
            <p className="mt-0.5 text-xs text-fg-muted">
              {showArchived
                ? t('app:chats.archived_count', {
                    count: archivedCount,
                    defaultValue: archivedCount + ' archiviert',
                  })
                : t('app:chats.active_count', {
                    count: activeCount,
                    defaultValue: activeCount + ' aktiv',
                  })}
            </p>
          </div>
          <div className="flex items-center gap-1">
            {!showArchived && (
              <button
                type="button"
                onClick={onNewGroup}
                aria-label={t('app:chats.new_group')}
                title={t('app:chats.new_group')}
                className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-fg-muted transition hover:bg-surface-3 hover:text-fg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
              >
                <AddUserIcon className="h-4 w-4" />
              </button>
            )}
          </div>
        </div>
      </header>

      <div className="px-3 pb-2">
        <label className="relative block">
          <span className="sr-only">{t('app:chats.search', { defaultValue: 'Suche' })}</span>
          <SearchIcon
            aria-hidden="true"
            className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-fg-muted"
          />
          <input
            type="search"
            value={query}
            onChange={(e) => onQueryChange(e.target.value)}
            placeholder={t('app:chats.search_placeholder', { defaultValue: 'Suche…' })}
            className="discord-composer w-full rounded-lg border border-transparent bg-surface-3 py-2 pl-9 pr-3 text-sm text-fg placeholder-fg-muted transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30"
          />
        </label>
      </div>

      <div className="px-3 pb-2">
        <div className="grid grid-cols-2 gap-1 rounded-lg bg-surface/70 p-1 dark:bg-[#232428]">
          <button
            type="button"
            onClick={() => {
              if (showArchived) onToggleArchived();
            }}
            className={
              'cursor-pointer rounded-md px-2 py-1.5 text-xs font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ' +
              (!showArchived
                ? 'bg-surface-3 text-fg shadow-sm dark:bg-[#383a40]'
                : 'text-fg-muted hover:text-fg')
            }
          >
            {t('app:chats.active_filter', { defaultValue: 'Aktiv' })}
          </button>
          <button
            type="button"
            onClick={() => {
              if (!showArchived) onToggleArchived();
            }}
            className={
              'relative cursor-pointer rounded-md px-2 py-1.5 text-xs font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ' +
              (showArchived
                ? 'bg-surface-3 text-fg shadow-sm dark:bg-[#383a40]'
                : 'text-fg-muted hover:text-fg')
            }
          >
            {t('app:chats.archive_filter', { defaultValue: 'Archiv' })}
            {archivedUnread > 0 && (
              <span className="ml-1 rounded-full bg-rose-500 px-1.5 py-0.5 text-[9px] font-bold text-white">
                {archivedUnread > 9 ? '9+' : archivedUnread}
              </span>
            )}
          </button>
        </div>
      </div>

      {loading ? (
        <div className="flex items-center gap-2 px-4 py-2 text-xs text-fg-muted">
          <SpinnerIcon className="h-3.5 w-3.5 text-accent" />
        </div>
      ) : error ? (
        <p className="px-4 py-2 text-xs text-rose-500 dark:text-rose-300">{error}</p>
      ) : items.length === 0 ? (
        <div className="flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center">
          <div className="flex h-12 w-12 items-center justify-center rounded-2xl border border-line bg-surface-3 text-accent">
            {showArchived ? (
              <ArchiveIcon className="h-5 w-5" />
            ) : (
              <ChatBubbleIcon className="h-5 w-5" />
            )}
          </div>
          <p className="text-sm text-fg-muted">
            {showArchived
              ? t('app:chats.archived_empty_title', { defaultValue: 'Nichts archiviert' })
              : t('app:chats.empty_title')}
          </p>
          <p className="text-xs text-fg-muted/80">
            {showArchived
              ? t('app:chats.archived_empty_subtitle', {
                  defaultValue: 'Archivierte Unterhaltungen erscheinen hier.',
                })
              : t('app:chats.empty_subtitle')}
          </p>
        </div>
      ) : (
        <VirtualConversationList
          items={items}
          activeId={activeId}
          unread={unread}
          onAccept={onAccept}
        />
      )}

      <div className="shrink-0 border-t border-line bg-surface-2 p-2">
        <UserBar />
      </div>
    </aside>
  );
}

const VLIST_INITIAL = 40;
const VLIST_STEP = 40;

function VirtualConversationList({
  items,
  activeId,
  unread,
  onAccept,
}: {
  items: ConversationSummary[];
  activeId: string | undefined;
  unread: Record<string, number>;
  onAccept: (id: string) => void;
}) {
  const [visible, setVisible] = useState<number>(VLIST_INITIAL);
  const scrollRef = useRef<HTMLUListElement | null>(null);
  const sentinelRef = useRef<HTMLLIElement | null>(null);

  useEffect(() => {
    setVisible(VLIST_INITIAL);
  }, [items.length]);

  useEffect(() => {
    if (items.length <= visible) return;
    const node = sentinelRef.current;
    if (!node) return;
    const obs = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting) {
          setVisible((n) => Math.min(items.length, n + VLIST_STEP));
        }
      },
      { root: scrollRef.current, rootMargin: '200px 0px' },
    );
    obs.observe(node);
    return () => obs.disconnect();
  }, [items.length, visible]);

  const slice = items.length <= VLIST_INITIAL ? items : items.slice(0, visible);
  const hasMore = items.length > slice.length;

  return (
    <ul ref={scrollRef} className="flex-1 overflow-y-auto px-2 pb-2">
      {slice.map((c) => (
        <li key={c.id}>
          <ConversationRow
            item={c}
            active={c.id === activeId}
            unreadCount={unread[c.id] ?? 0}
            onAccept={onAccept}
          />
        </li>
      ))}
      {hasMore && (
        <li ref={sentinelRef} className="py-2 text-center text-xs text-fg-muted">
          …
        </li>
      )}
    </ul>
  );
}

function ConversationRow({
  item,
  active,
  unreadCount,
  onAccept,
}: {
  item: ConversationSummary;
  active: boolean;
  unreadCount: number;
  onAccept: (id: string) => void;
}) {
  const { t } = useTranslation(['app']);
  const title = item.type === 'dm' ? (item.peer?.displayName ?? '?') : (item.name ?? '?');
  const handle = item.type === 'dm' ? '@' + (item.peer?.username ?? '?') : '';
  const avatarUrl = item.type === 'dm' ? (item.peer?.avatarUrl ?? null) : (item.avatarUrl ?? null);
  const preview =
    item.type === 'dm'
      ? handle
      : t('app:chats.group_preview', {
          count: item.members.length,
          defaultValue: `${item.members.length} Mitglieder`,
        });

  if (!item.acceptedByMe) {
    return (
      <div className="my-1 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3">
        <div className="flex items-center gap-3">
          <ConvAvatar url={avatarUrl} title={title} isGroup={item.type === 'group'} />
          <div className="min-w-0 flex-1">
            <p className="truncate text-sm font-semibold text-fg">{title}</p>
            <p className="truncate text-xs text-amber-600 dark:text-amber-200/80">
              {t('app:friends.incoming_request')}
            </p>
          </div>
        </div>
        <button
          type="button"
          onClick={() => onAccept(item.id)}
          className="mt-3 w-full cursor-pointer rounded-md bg-amber-500/20 px-3 py-1.5 text-xs font-semibold text-amber-700 transition hover:bg-amber-500/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-400/40 dark:text-amber-100"
        >
          {t('app:friends.action_accept')}
        </button>
      </div>
    );
  }

  const muted = isConversationMuted(item.mutedUntil);
  return (
    <NavLink
      to={'/chats/' + item.id}
      className={
        'group relative my-0.5 flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ' +
        (active
          ? 'bg-surface-3 text-fg shadow-sm dark:bg-[#404249]'
          : 'text-fg hover:bg-surface-3/70 dark:hover:bg-[#35373c]')
      }
    >
      {active && (
        <span
          aria-hidden="true"
          className="absolute left-0 top-1/2 h-7 w-1 -translate-y-1/2 rounded-r-full bg-accent"
        />
      )}
      <ConvAvatar url={avatarUrl} title={title} isGroup={item.type === 'group'} />
      <div className="min-w-0 flex-1">
        <div className="flex items-center gap-1.5">
          <p
            className={
              'truncate text-sm ' + (unreadCount > 0 && !muted ? 'font-bold' : 'font-semibold')
            }
          >
            {title}
          </p>
          {muted && <BellOffIcon aria-hidden="true" className="h-3 w-3 shrink-0 text-fg-muted" />}
          <ConversationVoiceDot conversationId={item.id} />
        </div>
        <p className="truncate text-xs text-fg-muted">{preview}</p>
      </div>
      {unreadCount > 0 && (
        <span
          aria-label={'Unread: ' + unreadCount}
          className={
            'inline-flex min-w-[20px] items-center justify-center rounded-full px-1.5 text-[10px] font-bold leading-tight ' +
            (muted ? 'bg-fg-muted/30 text-fg-muted' : 'bg-rose-500 text-white')
          }
        >
          {unreadCount > 99 ? '99+' : unreadCount}
        </span>
      )}
      <ConversationRowMenu
        conversationId={item.id}
        archived={item.archived}
        mutedUntil={item.mutedUntil}
      />
    </NavLink>
  );
}

/** Tiny green phone-icon next to the conversation title when somebody is
 *  currently in voice for this conversation. Discord-parity: surfaces voice
 *  activity in the chat list so users can hop in without opening the chat. */
function ConversationVoiceDot({ conversationId }: { conversationId: string }) {
  const present = useCallPresence(conversationId);
  if (present.length === 0) return null;
  return (
    <span
      aria-label={'Sprach-Channel aktiv: ' + present.length}
      title={'Sprach-Channel aktiv: ' + present.length}
      className="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full bg-emerald-500/20 text-emerald-500"
    >
      <PhoneIcon className="h-2.5 w-2.5" />
    </span>
  );
}

function ConvAvatar({
  url,
  title,
  isGroup,
}: {
  url: string | null;
  title: string;
  isGroup: boolean;
}) {
  if (url) {
    return (
      <img
        src={url}
        alt=""
        className="h-10 w-10 shrink-0 rounded-full object-cover"
        draggable={false}
      />
    );
  }
  if (isGroup) {
    return (
      <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent/20 text-accent dark:bg-accent/25 dark:text-white">
        <UsersIcon className="h-4 w-4" />
      </div>
    );
  }
  const letter = title.trim().charAt(0).toUpperCase() || '?';
  return (
    <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent/20 text-sm font-semibold text-accent dark:bg-accent/25 dark:text-white">
      {letter}
    </div>
  );
}

export function ChatsEmptyState() {
  const { t } = useTranslation(['app']);
  return (
    <div className="flex h-full items-center justify-center p-10">
      <div className="max-w-md text-center">
        <div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border border-line bg-surface-2 text-accent">
          <ChatBubbleIcon className="h-6 w-6" />
        </div>
        <h2 className="font-display text-2xl font-semibold text-fg">
          {t('app:chats.select_prompt')}
        </h2>
        <p className="mt-2 text-sm text-fg-muted">{t('app:chats.select_subtitle')}</p>
      </div>
    </div>
  );
}
