import React from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Bell, Check, ChevronRight, RefreshCcw } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { ToggleGroup } from '@tamagui/toggle-group';
import { motion, useAnimationControls, type PanInfo } from 'framer-motion';
import { MobileShell, HeaderActionButton } from './components/MobileShell';
import { MobileCard, PillBadge, SkeletonCard, CTAButton } from './components/Primitives';
import { MobileSelect } from './components/FormControls';
import { listNotificationLogs, markNotificationLogs, NotificationLogEntry } from '../api';
import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import toast from 'react-hot-toast';
import { MobileSheet } from './components/Sheet';
import { getEvents, TenantEvent } from '../api';
import { triggerHaptic } from './lib/haptics';
import { adminPath } from '../constants';
import { useBackNavigation } from './hooks/useBackNavigation';
import { groupNotificationsByScope, type NotificationScope, type NotificationGroup } from './lib/notificationGrouping';
import { collectUnreadIds } from './lib/notificationUnread';
import { formatRelativeTime } from './lib/relativeTime';
import { useAdminTheme, withAlpha } from './theme';

type NotificationItem = {
  id: string;
  title: string;
  body: string;
  time: string;
  tone: 'info' | 'warning';
  eventId?: number | null;
  eventName?: string | null;
  is_read?: boolean;
  scope: NotificationScope;
};

type NotificationSwipeRowProps = {
  item: NotificationItem;
  onOpen: (item: NotificationItem) => void;
  onMarkRead: (item: NotificationItem) => void;
  children: React.ReactNode;
};

function resolveEventName(name: TenantEvent['name'], t: (key: string, fallback?: string) => string): string {
  if (typeof name === 'string') {
    return name;
  }
  if (name && typeof name === 'object') {
    return name.de ?? name.en ?? Object.values(name)[0] ?? t('events.placeholders.untitled', 'Unbenanntes Event');
  }
  return t('events.placeholders.untitled', 'Unbenanntes Event');
}

function NotificationSwipeRow({ item, onOpen, onMarkRead, children }: NotificationSwipeRowProps) {
  const { t } = useTranslation('management');
  const { successBg, successText, infoBg, infoText } = useAdminTheme();
  const controls = useAnimationControls();
  const dragged = React.useRef(false);
  const markBg = successBg;
  const markText = successText;
  const detailBg = infoBg;
  const detailText = infoText;

  const handleDrag = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
    dragged.current = Math.abs(info.offset.x) > 6;
  };

  const handleDragEnd = (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {
    const swipeThreshold = 64;
    const offsetX = info.offset.x;
    if (offsetX > swipeThreshold && !item.is_read) {
      void onMarkRead(item);
    } else if (offsetX < -swipeThreshold) {
      onOpen(item);
    }
    dragged.current = false;
    void controls.start({ x: 0, transition: { type: 'spring', stiffness: 320, damping: 26 } });
  };

  const handlePress = () => {
    if (dragged.current) {
      dragged.current = false;
      return;
    }
    onOpen(item);
  };

  return (
    <YStack position="relative">
      <XStack
        alignItems="center"
        justifyContent="space-between"
        paddingHorizontal="$3"
        borderRadius="$4"
        pointerEvents="none"
        style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}
      >
        <XStack alignItems="center" gap="$2">
          <Check size={16} color={markText} />
          <Text fontSize="$xs" fontWeight="700" color={markText}>
            {item.is_read ? t('notificationLogs.read', 'Read') : t('notificationLogs.markRead', 'Mark read')}
          </Text>
        </XStack>
        <XStack alignItems="center" gap="$2">
          <Text fontSize="$xs" fontWeight="700" color={detailText}>
            Details
          </Text>
          <ChevronRight size={16} color={detailText} />
        </XStack>
      </XStack>
      <XStack
        borderRadius="$4"
        overflow="hidden"
        pointerEvents="none"
        backgroundColor={item.is_read ? detailBg : markBg}
        opacity={0.5}
        style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}
      />
      <motion.div
        drag="x"
        dragElastic={0.2}
        dragConstraints={{ left: -96, right: 96 }}
        onDrag={handleDrag}
        onDragEnd={handleDragEnd}
        animate={controls}
        initial={{ x: 0 }}
        style={{ touchAction: 'pan-y', position: 'relative', zIndex: 1 }}
      >
        <Pressable onPress={handlePress}>{children}</Pressable>
      </motion.div>
    </YStack>
  );
}

function formatLog(
  log: NotificationLogEntry,
  t: (key: string, defaultValue?: string, options?: Record<string, unknown>) => string,
  eventName?: string | null
): NotificationItem {
  const ctx = log.context ?? {};
  const limit = typeof ctx.limit === 'number' ? ctx.limit : null;
  const used = typeof ctx.used === 'number' ? ctx.used : null;
  const remaining = typeof ctx.remaining === 'number' ? ctx.remaining : null;
  const days = typeof ctx.day === 'number' ? ctx.day : null;
  const spendUsd = typeof ctx.spend_usd === 'number' ? ctx.spend_usd : null;
  const softCapUsd = typeof ctx.soft_cap_usd === 'number' ? ctx.soft_cap_usd : null;
  const hardCapUsd = typeof ctx.hard_cap_usd === 'number' ? ctx.hard_cap_usd : null;
  const failureCode = typeof ctx.failure_code === 'string' ? ctx.failure_code : null;
  const ctxEventId = ctx.event_id ?? ctx.eventId;
  const eventId = typeof ctxEventId === 'string' ? Number(ctxEventId) : (typeof ctxEventId === 'number' ? ctxEventId : null);
  const name = eventName ?? t('mobileNotifications.unknownEvent', 'Event');
  const isRead = log.is_read === true;
  const scope = (() => {
    switch (log.type) {
      case 'photo_limit':
      case 'photo_threshold':
        return 'photos';
      case 'guest_limit':
      case 'guest_threshold':
        return 'guests';
      case 'gallery_warning':
      case 'gallery_expired':
        return 'gallery';
      case 'event_limit':
      case 'event_threshold':
        return 'events';
      case 'package_expiring':
      case 'package_expired':
        return 'package';
      case 'ai_edit_succeeded':
      case 'ai_edit_failed':
      case 'ai_edit_blocked':
      case 'ai_budget_soft_cap':
      case 'ai_budget_hard_cap':
        return 'ai';
      default:
        return 'general';
    }
  })();

  switch (log.type) {
    case 'photo_limit':
      return {
        id: String(log.id),
        title: t('notificationLogs.photoLimit.title', 'Photo limit reached'),
        body: t('notificationLogs.photoLimit.body', '{{event}} reached its photo limit of {{limit}}.', {
          event: name,
          limit: limit ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'guest_limit':
      return {
        id: String(log.id),
        title: t('notificationLogs.guestLimit.title', 'Guest limit reached'),
        body: t('notificationLogs.guestLimit.body', '{{event}} reached its guest limit of {{limit}}.', {
          event: name,
          limit: limit ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'event_limit':
      return {
        id: String(log.id),
        title: t('notificationLogs.eventLimit.title', 'Event quota reached'),
        body: t('notificationLogs.eventLimit.body', 'Your package allows no more events. Limit: {{limit}}.', {
          limit: limit ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'gallery_warning':
      return {
        id: String(log.id),
        title: t('notificationLogs.galleryWarning.title', 'Gallery expiring soon'),
        body: t('notificationLogs.galleryWarning.body', '{{event}} expires in {{days}} days.', {
          event: name,
          days: days ?? ctx.threshold ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'gallery_expired':
      return {
        id: String(log.id),
        title: t('notificationLogs.galleryExpired.title', 'Gallery expired'),
        body: t('notificationLogs.galleryExpired.body', '{{event}} gallery is offline. Extend to reactivate.', {
          event: name,
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'photo_threshold':
      return {
        id: String(log.id),
        title: t('notificationLogs.photoThreshold.title', 'Photo usage warning'),
        body: t('notificationLogs.photoThreshold.body', '{{event}} is at {{used}} / {{limit}} photos.', {
          event: name,
          used: used ?? '—',
          limit: limit ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'info',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'guest_threshold':
      return {
        id: String(log.id),
        title: t('notificationLogs.guestThreshold.title', 'Guest usage warning'),
        body: t('notificationLogs.guestThreshold.body', '{{event}} is at {{used}} / {{limit}} guests.', {
          event: name,
          used: used ?? '—',
          limit: limit ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'info',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'ai_edit_succeeded':
      return {
        id: String(log.id),
        title: t('notificationLogs.aiEditSucceeded.title', 'AI edit completed'),
        body: t('notificationLogs.aiEditSucceeded.body', '{{event}} has a completed AI edit ready.', {
          event: name,
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'info',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'ai_edit_failed':
      return {
        id: String(log.id),
        title: t('notificationLogs.aiEditFailed.title', 'AI edit failed'),
        body: t('notificationLogs.aiEditFailed.body', '{{event}} could not finish an AI edit. Reason: {{reason}}.', {
          event: name,
          reason: failureCode ?? t('notificationLogs.aiEditFailed.reasonUnknown', 'unknown'),
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'ai_edit_blocked':
      return {
        id: String(log.id),
        title: t('notificationLogs.aiEditBlocked.title', 'AI edit blocked'),
        body: t('notificationLogs.aiEditBlocked.body', '{{event}} AI edit was blocked by safety checks.', {
          event: name,
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'ai_budget_soft_cap':
      return {
        id: String(log.id),
        title: t('notificationLogs.aiBudgetSoftCap.title', 'AI budget warning'),
        body: t('notificationLogs.aiBudgetSoftCap.body', '{{event}} reached {{spend}} USD of {{cap}} USD AI budget.', {
          event: name,
          spend: spendUsd ?? '—',
          cap: softCapUsd ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'info',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    case 'ai_budget_hard_cap':
      return {
        id: String(log.id),
        title: t('notificationLogs.aiBudgetHardCap.title', 'AI budget exhausted'),
        body: t('notificationLogs.aiBudgetHardCap.body', '{{event}} reached the hard AI budget cap of {{cap}} USD.', {
          event: name,
          cap: hardCapUsd ?? '—',
        }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'warning',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
    default:
      return {
        id: String(log.id),
        title: log.type,
        body: t('notificationLogs.generic.body', 'Notification sent via {{channel}}.', { channel: log.channel }),
        time: log.sent_at ?? log.failed_at ?? '',
        tone: 'info',
        eventId,
        eventName,
        is_read: isRead,
        scope,
      };
  }
}

async function loadNotifications(
  t: (key: string, defaultValue?: string, options?: Record<string, unknown>) => string,
  events?: TenantEvent[],
  filters?: { scope?: string; status?: string; eventSlug?: string }
): Promise<NotificationItem[]> {
  try {
    const eventId = filters?.eventSlug ? (events ?? []).find((ev) => ev.slug === filters.eventSlug)?.id ?? undefined : undefined;
    const response = await listNotificationLogs({
      perPage: 50,
      scope: filters?.scope && filters.scope !== 'all' ? filters.scope : undefined,
      status: filters?.status === 'all' ? undefined : filters?.status,
      eventId: eventId,
    });
    const lookup = new Map<number, string>();
    (events ?? []).forEach((event) => {
      lookup.set(event.id, resolveEventName(event.name, t));
    });

    return (response.data ?? [])
      .map((log) => {
        const ctxEventId = log.context?.event_id ?? log.context?.eventId;
        const parsed = typeof ctxEventId === 'string' ? Number(ctxEventId) : ctxEventId;
        return formatLog(log, t, lookup.get(parsed as number));
      });
  } catch (err) {
    throw err;
  }
}

export default function MobileNotificationsPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const { notificationId } = useParams<{ notificationId?: string }>();
  const { t } = useTranslation('management');
  const search = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
  const slug = search.get('event') ?? undefined;
  const scopeParam = search.get('scope') ?? 'all';
  const statusParam = search.get('status') ?? 'unread';
  const [notifications, setNotifications] = React.useState<NotificationItem[]>([]);
  const [selectedNotification, setSelectedNotification] = React.useState<NotificationItem | null>(null);
  const [detailOpen, setDetailOpen] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState<string | null>(null);
  const [events, setEvents] = React.useState<TenantEvent[]>([]);
  const [showEventPicker, setShowEventPicker] = React.useState(false);
  const back = useBackNavigation(adminPath('/mobile/dashboard'));
  const { text, muted, border, warningBg, warningText, infoBg, primary, danger, subtle, accentSoft } = useAdminTheme();
  const activePillBg = accentSoft ?? withAlpha(primary, 0.18);
  const activePillBorder = withAlpha(primary, 0.45);
  const warningIcon = warningText;
  const infoIcon = primary;
  const errorText = danger;

  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      const data = await loadNotifications(t as any, events, { scope: scopeParam, status: statusParam, eventSlug: slug });
      setNotifications(data);
      setError(null);
    } catch (err) {
      if (!isAuthError(err)) {
        const message = getApiErrorMessage(err, t('events.errors.loadFailed', 'Benachrichtigungen konnten nicht geladen werden.'));
        setError(message);
        toast.error(message);
      }
    } finally {
      setLoading(false);
    }
  }, [slug, t, events]);

  React.useEffect(() => {
    void reload();
  }, [reload]);

  React.useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      if (event.data?.type === 'admin-notification-refresh') {
        void reload();
      }
    };

    navigator.serviceWorker?.addEventListener('message', handleMessage);

    return () => {
      navigator.serviceWorker?.removeEventListener('message', handleMessage);
    };
  }, [reload]);

  React.useEffect(() => {
    const interval = window.setInterval(() => {
      void reload();
    }, 90_000);

    return () => {
      window.clearInterval(interval);
    };
  }, [reload]);

  React.useEffect(() => {
    (async () => {
      try {
        const list = await getEvents();
        setEvents(list);
      } catch {
        // non-fatal
      }
    })();
  }, []);

  const filtered = React.useMemo(() => {
    if (!slug) return notifications;
    const target = events.find((ev) => ev.slug === slug);
    if (!target) return notifications;
    return notifications.filter((item) => item.eventId === target.id || item.body.includes(String(target.name)) || item.title.includes(String(target.name)));
  }, [notifications, slug, events]);

  const scoped = React.useMemo(() => {
    if (scopeParam === 'all') return filtered;
    return filtered.filter((item) => item.scope === scopeParam);
  }, [filtered, scopeParam]);

  const statusFiltered = React.useMemo(() => {
    if (statusParam === 'all') return scoped;
    if (statusParam === 'read') return scoped.filter((item) => item.is_read);
    return scoped.filter((item) => !item.is_read);
  }, [scoped, statusParam]);

  const grouped = React.useMemo(() => groupNotificationsByScope(statusFiltered), [statusFiltered]);

  const unreadIds = React.useMemo(() => collectUnreadIds(scoped), [scoped]);

  const showFilterNotice = Boolean(slug) && filtered.length === 0 && notifications.length > 0;

  const markNotificationRead = React.useCallback(
    async (item: NotificationItem, options?: { close?: boolean }) => {
      const id = Number(item.id);
      if (!Number.isFinite(id)) return;
      try {
        await markNotificationLogs([id], 'read');
        await reload();
        triggerHaptic('success');
        if (options?.close) {
          setDetailOpen(false);
          setSelectedNotification(null);
        }
      } catch {
        toast.error(t('notificationLogs.markFailed', 'Could not update notifications.'));
      }
    },
    [reload, t],
  );

  const markSelectedRead = React.useCallback(async () => {
    if (!selectedNotification) return;
    await markNotificationRead(selectedNotification, { close: true });
  }, [markNotificationRead, selectedNotification]);

  const notificationListPath = adminPath('/mobile/notifications');
  const updateFilters = React.useCallback(
    (params: { scope?: string; status?: string; event?: string | null }) => {
      const next = new URLSearchParams({
        status: params.status ?? statusParam,
        scope: params.scope ?? scopeParam,
        event: params.event ?? slug ?? '',
      });
      navigate(`${notificationListPath}?${next.toString()}`, { replace: false });
    },
    [navigate, notificationListPath, scopeParam, slug, statusParam],
  );

  const markGroupRead = React.useCallback(
    async (group: NotificationGroup<NotificationItem>) => {
      const ids = collectUnreadIds(group.items);
      if (!ids.length) {
        return;
      }
      try {
        await markNotificationLogs(ids, 'read');
        void reload();
        triggerHaptic('success');
      } catch {
        toast.error(t('notificationLogs.markFailed', 'Could not update notifications.'));
      }
    },
    [reload, t],
  );

  const openNotification = React.useCallback(
    (item: NotificationItem) => {
      setSelectedNotification(item);
      setDetailOpen(true);
      if (notificationId !== String(item.id)) {
        navigate(`${notificationListPath}/${item.id}${location.search}`, { replace: false });
      }
      triggerHaptic('light');
    },
    [location.search, navigate, notificationId, notificationListPath],
  );

  React.useEffect(() => {
    if (!notificationId || loading) {
      return;
    }
    const targetId = Number(notificationId);
    if (!Number.isFinite(targetId)) {
      return;
    }
    const target = notifications.find((item) => Number(item.id) === targetId);
    if (target) {
      setSelectedNotification(target);
      setDetailOpen(true);
    }
  }, [notificationId, notifications, loading]);

  return (
    <MobileShell
      activeTab="home"
      title={t('mobileNotifications.title', 'Notifications')}
      onBack={back}
      headerActions={
        <HeaderActionButton onPress={() => reload()} ariaLabel={t('common.refresh', 'Refresh')}>
          <RefreshCcw size={18} color={text} />
        </HeaderActionButton>
      }
    >
      {error ? (
        <MobileCard>
          <Text fontWeight="700" color={errorText}>
            {error}
          </Text>
          <CTAButton
            label={t('common.retry', 'Retry')}
            tone="ghost"
            fullWidth={false}
            onPress={() => reload()}
          />
        </MobileCard>
      ) : null}

      {showFilterNotice ? (
        <MobileCard gap="$2">
          <Text fontSize="$sm" fontWeight="700" color={text}>
            {t('notificationLogs.filterEmpty', 'No notifications for this event.')}
          </Text>
          <Pressable
            onPress={() => {
              updateFilters({ event: '' });
            }}
          >
            <Text fontSize="$sm" color={primary} fontWeight="700">
              {t('notificationLogs.clearFilter', 'Show all notifications')}
            </Text>
          </Pressable>
        </MobileCard>
      ) : null}

      <XStack gap="$2" marginBottom="$2">
        <MobileSelect
          value={statusParam}
          onChange={(e) => updateFilters({ status: e.target.value })}
          compact
          style={{ minWidth: 120 }}
        >
          <option value="unread">{t('notificationLogs.filter.unread', 'Unread')}</option>
          <option value="read">{t('notificationLogs.filter.read', 'Read')}</option>
          <option value="all">{t('notificationLogs.filter.all', 'All')}</option>
        </MobileSelect>
        {unreadIds.length ? (
          <CTAButton
            label={t('notificationLogs.markAllRead', 'Mark all read')}
            onPress={async () => {
              try {
                await markNotificationLogs(unreadIds, 'read');
                void reload();
                triggerHaptic('success');
              } catch {
                toast.error(t('notificationLogs.markFailed', 'Could not update notifications.'));
              }
            }}
            tone="ghost"
          />
        ) : null}
      </XStack>

      <ToggleGroup
        type="single"
        value={scopeParam ?? 'all'}
        onValueChange={(value: string) => value && updateFilters({ scope: value as NotificationScope | 'all' })}
        disableDeactivation
        orientation="horizontal"
        flexDirection="row"
        flexWrap="wrap"
        gap="$2"
        padding="$1"
        borderRadius="$pill"
        borderWidth={1}
        borderColor={border}
        backgroundColor="$background"
        marginBottom="$2"
      >
        {([
          { key: 'all', label: t('notificationLogs.scope.all', 'All scopes') },
          { key: 'photos', label: t('notificationLogs.scope.photos', 'Photos') },
          { key: 'guests', label: t('notificationLogs.scope.guests', 'Guests') },
          { key: 'gallery', label: t('notificationLogs.scope.gallery', 'Gallery') },
          { key: 'events', label: t('notificationLogs.scope.events', 'Events') },
          { key: 'package', label: t('notificationLogs.scope.package', 'Package') },
          { key: 'ai', label: t('notificationLogs.scope.ai', 'AI') },
          { key: 'general', label: t('notificationLogs.scope.general', 'General') },
        ] as Array<{ key: NotificationScope | 'all'; label: string }>).map((filter) => {
          const isActive = (scopeParam ?? 'all') === filter.key;
          return (
          <ToggleGroup.Item
            key={filter.key}
            value={filter.key}
            borderRadius="$pill"
            borderWidth={1}
            borderColor={isActive ? activePillBorder : border}
            backgroundColor={isActive ? activePillBg : 'transparent'}
            paddingVertical="$2"
            paddingHorizontal="$3"
            flexGrow={1}
            justifyContent="center"
            width="auto"
            minWidth="max-content"
          >
            <Text fontSize="$xs" fontWeight={isActive ? '700' : '600'} textAlign="center" color={isActive ? text : muted}>
              {filter.label}
            </Text>
          </ToggleGroup.Item>
          );
        })}
      </ToggleGroup>

      {loading ? (
        <YStack gap="$2">
          {Array.from({ length: 4 }).map((_, idx) => (
            <SkeletonCard key={`al-${idx}`} height={70} />
          ))}
        </YStack>
      ) : statusFiltered.length === 0 ? (
        <MobileCard alignItems="center" justifyContent="center" gap="$2">
          <Bell size={24} color={subtle} />
          <Text fontSize="$sm" fontWeight="700" color={text}>
            {t('mobileNotifications.emptyTitle', 'All caught up')}
          </Text>
          <Text fontSize="$xs" color={muted} textAlign="center">
            {t('mobileNotifications.emptyBody', 'Enable push to receive alerts about uploads, guests, and expiring galleries.')}
          </Text>
          <CTAButton
            label={t('mobileNotifications.emptyAction', 'Check notification settings')}
            tone="ghost"
            fullWidth={false}
            onPress={() => navigate(adminPath('/mobile/settings'))}
          />
        </MobileCard>
      ) : (
        <YStack gap="$2">
          {events.length ? (
            <Pressable onPress={() => setShowEventPicker(true)}>
              <Text fontSize="$sm" color={primary} fontWeight="700">
                {t('mobileNotifications.filterByEvent', 'Nach Event filtern')}
              </Text>
            </Pressable>
          ) : null}
          {grouped.map((group) => (
            <YStack key={group.scope} gap="$2">
              <XStack alignItems="center" justifyContent="space-between">
                <Text fontSize="$xs" fontWeight="700" color={muted}>
                  {t(`notificationLogs.scope.${group.scope}`, group.scope)}
                </Text>
                <XStack gap="$2" alignItems="center">
                  {group.unread > 0 ? (
                    <Pressable onPress={() => markGroupRead(group)}>
                      <Text fontSize="$xs" color={primary} fontWeight="700">
                        {t('notificationLogs.markScopeRead', 'Mark read')}
                      </Text>
                    </Pressable>
                  ) : null}
                  {group.unread > 0 ? (
                    <PillBadge tone="warning">
                      {t('notificationLogs.unread', 'Unread')} {group.unread}
                    </PillBadge>
                  ) : null}
                  <PillBadge tone="muted">{group.items.length}</PillBadge>
                </XStack>
              </XStack>
              {group.items.map((item) => {
                const formattedTime = formatRelativeTime(item.time) || item.time || '—';
                return (
                <NotificationSwipeRow
                  key={item.id}
                  item={item}
                  onOpen={openNotification}
                  onMarkRead={markNotificationRead}
                >
                  <MobileCard gap="$2" borderColor={item.is_read ? border : primary}>
                    <XStack alignItems="center" gap="$2">
                      <XStack
                        width={36}
                        height={36}
                        borderRadius={12}
                        alignItems="center"
                        justifyContent="center"
                        backgroundColor={item.tone === 'warning' ? warningBg : infoBg}
                      >
                        <Bell size={18} color={item.tone === 'warning' ? warningIcon : infoIcon} />
                      </XStack>
                      <YStack gap="$0.5" flex={1}>
                        <Text fontSize="$sm" fontWeight="700" color={text}>
                          {item.title}
                        </Text>
                        <Text fontSize="$xs" color={muted}>
                          {item.body}
                        </Text>
                        {item.eventName ? (
                          <PillBadge tone="muted">{item.eventName}</PillBadge>
                        ) : null}
                      </YStack>
                      {!item.is_read ? <PillBadge tone="warning">{t('notificationLogs.unread', 'Unread')}</PillBadge> : null}
                      <PillBadge tone={item.tone === 'warning' ? 'warning' : 'muted'}>{formattedTime}</PillBadge>
                    </XStack>
                  </MobileCard>
                </NotificationSwipeRow>
              );
              })}
            </YStack>
          ))}
        </YStack>
      )}

      <MobileSheet
        open={detailOpen && Boolean(selectedNotification)}
        onClose={() => {
          setDetailOpen(false);
          setSelectedNotification(null);
          if (notificationId) {
            navigate(`${notificationListPath}${location.search}`, { replace: true });
          }
        }}
        title={selectedNotification?.title ?? t('mobileNotifications.title', 'Notifications')}
        snapPoints={[94]}
        footer={
          selectedNotification && !selectedNotification.is_read ? (
            <CTAButton label={t('notificationLogs.markRead', 'Mark as read')} onPress={() => markSelectedRead()} />
          ) : null
        }
      >
        {selectedNotification ? (
          <YStack gap="$2">
            <Text fontSize="$sm" color={text} fontWeight="700">
              {selectedNotification.title}
            </Text>
            <Text fontSize="$sm" color={muted}>
              {selectedNotification.body}
            </Text>
            <XStack gap="$2" flexWrap="wrap" style={{ rowGap: 8 }}>
              <PillBadge tone={selectedNotification.tone === 'warning' ? 'warning' : 'muted'}>
                {selectedNotification.scope}
              </PillBadge>
              {!selectedNotification.is_read ? <PillBadge tone="warning">{t('notificationLogs.unread', 'Unread')}</PillBadge> : null}
            </XStack>
            <Text fontSize="$xs" color={muted}>
              {formatRelativeTime(selectedNotification.time) || selectedNotification.time || '—'}
            </Text>
          </YStack>
        ) : null}
      </MobileSheet>

      <MobileSheet
        open={showEventPicker}
        onClose={() => setShowEventPicker(false)}
        title={t('mobileNotifications.filterByEvent', 'Nach Event filtern')}
        footer={null}
      >
        <YStack gap="$2">
          {events.length === 0 ? (
            <Text fontSize="$sm" color={muted}>
              {t('events.list.empty.description', 'Starte jetzt mit deinem ersten Event.')}
            </Text>
          ) : (
            events.map((ev) => (
              <Pressable
                key={ev.slug}
                onPress={() => {
                  setShowEventPicker(false);
                  if (ev.slug) {
                    updateFilters({ event: ev.slug });
                  }
                }}
              >
                <XStack alignItems="center" justifyContent="space-between" paddingVertical="$2">
                  <YStack>
                    <Text fontSize="$sm" fontWeight="700" color={text}>
                      {resolveEventName(ev.name, t)}
                    </Text>
                    <Text fontSize="$xs" color={muted}>
                      {ev.slug}
                    </Text>
                  </YStack>
                  <PillBadge tone="muted">{ev.status ?? '—'}</PillBadge>
                </XStack>
              </Pressable>
            ))
          )}
        </YStack>
      </MobileSheet>
    </MobileShell>
  );
}
