import { Component, createSignal, Show, For, createMemo, createEffect } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import { useWebSocket } from '@/App';
import { formatBytes, formatAbsoluteTime, formatRelativeTime, formatUptime, formatPercent } from '@/utils/format';
import { createLocalStorageBooleanSignal, STORAGE_KEYS } from '@/utils/localStorage';
import { parseFilterStack, evaluateFilterStack } from '@/utils/searchQuery';
import { UnifiedNodeSelector } from '@/components/shared/UnifiedNodeSelector';
import { MetricBar } from '@/components/Dashboard/MetricBar';
import { BackupsFilter } from './BackupsFilter';
import { Card } from '@/components/shared/Card';
import { EmptyState } from '@/components/shared/EmptyState';
import { SectionHeader } from '@/components/shared/SectionHeader';
import { showTooltip, hideTooltip } from '@/components/shared/Tooltip';
import type { BackupType, GuestType, UnifiedBackup } from '@/types/backups';
import { usePersistentSignal } from '@/hooks/usePersistentSignal';
import { logger } from '@/utils/logger';

type BackupSortKey = keyof Pick<
  UnifiedBackup,
  | 'backupTime'
  | 'name'
  | 'node'
  | 'vmid'
  | 'backupType'
  | 'size'
  | 'storage'
  | 'verified'
  | 'type'
  | 'owner'
>;
const BACKUP_SORT_KEY_VALUES: readonly BackupSortKey[] = [
  'backupTime',
  'name',
  'node',
  'vmid',
  'backupType',
  'size',
  'storage',
  'verified',
  'type',
  'owner',
] as const;

type FilterableGuestType = 'VM' | 'LXC' | 'Host';

interface DateGroup {
  label: string;
  items: UnifiedBackup[];
}

const UnifiedBackups: Component = () => {
  const navigate = useNavigate();
  const { state, connected, initialDataReceived } = useWebSocket();
  const pveBackupsState = createMemo(() => state.backups?.pve ?? state.pveBackups);
  const pbsBackupsState = createMemo(() => state.backups?.pbs ?? state.pbsBackups);
  const pmgBackupsState = createMemo(() => state.backups?.pmg ?? state.pmgBackups);
  const [searchTerm, setSearchTerm] = createSignal('');
  const [selectedNode, setSelectedNode] = createSignal<string | null>(null);
  const [typeFilter, setTypeFilter] = createSignal<'all' | FilterableGuestType>('all');
  const [backupTypeFilter, setBackupTypeFilter] = createSignal<'all' | BackupType>('all');
  const [groupByMode, setGroupByMode] = createSignal<'date' | 'guest'>('date');

  // Convert between UI filter and internal filter for BackupsFilter component
  const uiBackupTypeFilter = createMemo(() => {
    const filter = backupTypeFilter();
    if (filter === 'all') return 'all';
    if (filter === 'snapshot') return 'snapshot';
    if (filter === 'local') return 'pve';
    if (filter === 'remote') return 'pbs';
    return 'all';
  });

  const setUiBackupTypeFilter = (value: 'all' | 'snapshot' | 'pve' | 'pbs') => {
    if (value === 'all') setBackupTypeFilter('all');
    else if (value === 'snapshot') setBackupTypeFilter('snapshot');
    else if (value === 'pve') setBackupTypeFilter('local');
    else if (value === 'pbs') setBackupTypeFilter('remote');
  };
  const [sortKey, setSortKey] = usePersistentSignal<BackupSortKey>('backupsSortKey', 'backupTime', {
    deserialize: (raw) =>
      BACKUP_SORT_KEY_VALUES.includes(raw as BackupSortKey) ? (raw as BackupSortKey) : 'backupTime',
  });
  const [sortDirection, setSortDirection] = usePersistentSignal<'asc' | 'desc'>(
    'backupsSortDirection',
    'desc',
    {
      deserialize: (raw) => (raw === 'asc' || raw === 'desc' ? raw : 'desc'),
    },
  );
  const [selectedDateRange, setSelectedDateRange] = createSignal<{
    start: number;
    end: number;
  } | null>(null);
  const [chartTimeRange, setChartTimeRange] = createSignal(30);
  const [isSearchLocked, setIsSearchLocked] = createSignal(false);
  const availableBackupsTooltipText =
    'Daily counts of backups still available for restore across snapshots, PVE storage, and PBS.';

  const sortKeyOptions: { value: BackupSortKey; label: string }[] = [
    { value: 'backupTime', label: 'Time' },
    { value: 'name', label: 'Guest Name' },
    { value: 'node', label: 'Node' },
    { value: 'vmid', label: 'VMID' },
    { value: 'backupType', label: 'Backup Type' },
    { value: 'size', label: 'Size' },
    { value: 'storage', label: 'Storage' },
    { value: 'verified', label: 'Verified' },
    { value: 'type', label: 'Guest Type' },
    { value: 'owner', label: 'Owner' },
  ];

  // Extract PBS instance from search term
  const selectedPBSInstance = createMemo(() => {
    const search = searchTerm();
    const match = search.match(/node:(\S+)/);
    if (match && state.pbs?.some((pbs) => pbs.name === match[1])) {
      return match[1];
    }
    return null;
  });

  // Auto-set backup type filter when PBS instance is selected
  createEffect(() => {
    const pbsInstance = selectedPBSInstance();
    if (pbsInstance) {
      setBackupTypeFilter('remote');
    } else if (!isSearchLocked()) {
      setBackupTypeFilter('all');
    }
  });

  const [useRelativeTime] = createLocalStorageBooleanSignal(
    STORAGE_KEYS.BACKUPS_USE_RELATIVE_TIME,
    false, // Default to absolute time
  );
  // TODO: Add time format toggle to BackupsFilter component
  // const setUseRelativeTime = ...;

  // Helper functions
  const getDaySuffix = (day: number) => {
    if (day >= 11 && day <= 13) return 'th';
    switch (day % 10) {
      case 1:
        return 'st';
      case 2:
        return 'nd';
      case 3:
        return 'rd';
      default:
        return 'th';
    }
  };

  const truncateMiddle = (str: string, maxLength: number) => {
    if (!str || str.length <= maxLength) return str;
    const start = Math.ceil(maxLength / 2) - 2;
    const end = Math.floor(maxLength / 2) - 2;
    return str.substring(0, start) + '...' + str.substring(str.length - end);
  };

  const formatTime = (timestamp: number) => {
    return useRelativeTime() ? formatRelativeTime(timestamp) : formatAbsoluteTime(timestamp);
  };

  // Check if we have any backup data yet
  const isLoading = createMemo(() => {
    const pve = pveBackupsState();
    const pbs = pbsBackupsState();
    const pmg = pmgBackupsState();
    const hasData =
      (pve?.guestSnapshots?.length ?? 0) > 0 ||
      (pve?.storageBackups?.length ?? 0) > 0 ||
      (pbs?.length ?? 0) > 0 ||
      (pmg?.length ?? 0) > 0;
    if (hasData) {
      return false;
    }
    if (!connected()) {
      return true;
    }
    if (!initialDataReceived()) {
      return true;
    }
    const hasLastUpdate = Boolean(state.lastUpdate);
    return !hasLastUpdate;
  });

  // Normalize all backup data into unified format
  const normalizedData = createMemo(() => {
    const unified: UnifiedBackup[] = [];
    const seenBackups = new Set<string>(); // Track backups to avoid duplicates

    // Debug mode - can be enabled via console: localStorage.setItem('debug-pmg', 'true')
    const debugMode = typeof window !== 'undefined' && localStorage.getItem('debug-pmg') === 'true';

    // Normalize snapshots
    pveBackupsState()?.guestSnapshots?.forEach((snapshot) => {
      // Try to find the guest name by matching VMID and instance (not hostname)
      let guestName = '';
      const vm = state.vms?.find(
        (v) => v.vmid === snapshot.vmid && v.instance === snapshot.instance,
      );
      const ct = state.containers?.find(
        (c) => c.vmid === snapshot.vmid && c.instance === snapshot.instance,
      );
      if (vm) {
        guestName = vm.name || '';
      } else if (ct) {
        guestName = ct.name || '';
      }

      unified.push({
        backupType: 'snapshot',
        vmid: snapshot.vmid,
        name: guestName || `VM ${snapshot.vmid}`, // Use guest name if found, otherwise fallback to VMID
        type: snapshot.type === 'qemu' ? 'VM' : 'LXC',
        node: snapshot.node,
        instance: snapshot.instance, // Unique instance ID for handling duplicate node names
        backupTime: snapshot.time ? new Date(snapshot.time).getTime() / 1000 : 0,
        backupName: snapshot.name, // This is the snapshot name like "current", "pre-upgrade"
        description: snapshot.description || '',
        status: 'ok',
        size: typeof snapshot.sizeBytes === 'number' ? snapshot.sizeBytes : null,
        storage: null,
        datastore: null,
        namespace: null,
        verified: null,
        protected: false,
      });
    });

    // Process PBS backups FIRST from the new Go backend (state.pbsBackups)
    // This ensures we have the complete PBS data with namespaces
    // Filter by selected PBS instance if one is selected
    const pbsBackupsToProcess = selectedPBSInstance()
      ? pbsBackupsState()?.filter((b) => b.instance === selectedPBSInstance())
      : pbsBackupsState();

    pbsBackupsToProcess?.forEach((backup) => {
      const backupDate = new Date(backup.backupTime);
      const dateStr = backupDate.toISOString().split('T')[0];
      const timeStr = backupDate.toISOString().split('T')[1].split('.')[0].replace(/:/g, '');
      const backupName = `${backup.backupType}/${backup.vmid}/${dateStr}_${timeStr}`;

      // Create a key that matches the format used by PVE storage backups
      // Use just the timestamp in seconds (Unix time) to match ctime format
      const backupTimeSeconds = Math.floor(backupDate.getTime() / 1000);
      const backupKey = `${backup.vmid}-${backupTimeSeconds}`;
      seenBackups.add(backupKey);

      if (debugMode) {
        logger.debug(
          `PBS backup: vmid=${backup.vmid}, time=${backupTimeSeconds}, key=${backupKey}, verified=${backup.verified}`,
        );
      }

      // Check if any files have encryption
      const isEncrypted =
        backup.files &&
        Array.isArray(backup.files) &&
        backup.files.some((file) => {
          if (typeof file === 'string') return false;
          const fileObj = file as Record<string, unknown>;
          return (
            fileObj.crypt ||
            fileObj.encrypted ||
            (typeof fileObj.filename === 'string' && fileObj.filename.includes('.enc'))
          );
        });

      // Determine the display type based on VMID and backup type
      // VMID 0 = host config backup (e.g. PMG)
      // PBS stores PMG backups as 'ct' type with VMID='0'
      let displayType: GuestType;

      // Check for VMID=0 which indicates host backup (handle both string and number)
      const vmidAsNumber =
        typeof backup.vmid === 'string' ? parseInt(backup.vmid, 10) : backup.vmid;
      const isVmidZero = vmidAsNumber === 0;

      if (debugMode && isVmidZero) {
        logger.debug('[PMG Debug] PBS backup with VMID=0:', {
          vmid: backup.vmid,
          vmidType: typeof backup.vmid,
          backupType: backup.backupType,
          instance: backup.instance,
          comment: backup.comment,
          willBeMarkedAs: isVmidZero ? 'Host' : backup.backupType === 'ct' ? 'LXC' : 'Unknown',
        });
      }

      if (isVmidZero || backup.backupType === 'host') {
        displayType = 'Host';
      } else if (backup.backupType === 'vm' || backup.backupType === 'VM') {
        displayType = 'VM';
      } else if (backup.backupType === 'ct' || backup.backupType === 'lxc') {
        displayType = 'LXC';
      } else {
        // Default fallback
        displayType = 'LXC';
      }

      unified.push({
        backupType: 'remote',
        vmid: displayType === 'Host' ? backup.vmid : parseInt(backup.vmid) || 0,
        name: backup.comment || '',
        type: displayType,
        node: backup.instance || 'PBS',
        instance: backup.instance || 'PBS',
        backupTime: backupTimeSeconds,
        backupName: backupName,
        description: backup.comment || '',
        status: backup.verified ? 'verified' : 'unverified',
        size: backup.size || null,
        storage: null,
        datastore: backup.datastore || null,
        namespace: backup.namespace || 'root',
        verified: backup.verified || false,
        protected: backup.protected || false,
        encrypted: isEncrypted,
        owner: backup.owner,
      });
    });

    pmgBackupsState()?.forEach((backup) => {
      const backupTimeMs = backup.backupTime ? Date.parse(backup.backupTime) : Number.NaN;
      const backupTimeSeconds = Number.isFinite(backupTimeMs) ? Math.floor(backupTimeMs / 1000) : 0;
      const backupKey = backup.id || `${backup.instance}:${backup.node}:${backup.filename}`;
      if (seenBackups.has(backupKey)) {
        return;
      }
      seenBackups.add(backupKey);

      const displayName = backup.node || backup.filename || 'PMG Host Backup';
      unified.push({
        backupType: 'local',
        vmid: backup.node || backup.filename,
        name: displayName,
        type: 'Host',
        node: backup.node || 'PMG',
        instance: backup.instance || 'PMG',
        backupTime: backupTimeSeconds,
        backupName: backup.filename || displayName,
        description: backup.filename || '',
        status: 'ok',
        size: backup.size || null,
        storage: 'PMG',
        datastore: null,
        namespace: null,
        verified: null,
        protected: false,
      });
    });

    // Normalize local backups (including PBS through PVE storage)
    pveBackupsState()?.storageBackups?.forEach((backup) => {
      // Skip templates and ISOs - they're not backups
      if (backup.type === 'vztmpl' || backup.type === 'iso') {
        return;
      }

      // Determine if this is actually a PBS backup based on storage
      const backupType = backup.isPBS ? 'remote' : 'local';

      // Create a key to check for duplicates - use VMID and timestamp
      // This prevents the same backup from appearing twice if it's in multiple storages
      const backupKey = `${backup.vmid}-${backup.ctime}`;

      // Skip if we've already seen this backup (PBS or local duplicate)
      if (seenBackups.has(backupKey)) {
        if (debugMode) {
          logger.debug(
            `PVE storage backup duplicate skipped: vmid=${backup.vmid}, ctime=${backup.ctime}, key=${backupKey}, storage=${backup.storage}`,
          );
        }
        return; // Skip duplicate
      }
      seenBackups.add(backupKey); // Mark as seen to prevent duplicates

      // Determine the display type based on backup.type and VMID
      // VMID 0 = host config backup (e.g. PMG/PVE host configs)
      let displayType: GuestType;

      // Check for VMID=0 which indicates host backup
      const vmidAsNumber =
        typeof backup.vmid === 'string' ? parseInt(backup.vmid, 10) : backup.vmid;
      const isVmidZero = vmidAsNumber === 0;

      if (debugMode && isVmidZero) {
        logger.debug('[PMG Debug] Storage backup with VMID=0:', {
          vmid: backup.vmid,
          vmidType: typeof backup.vmid,
          type: backup.type,
          volid: backup.volid,
          notes: backup.notes,
          willBeMarkedAs: isVmidZero ? 'Host' : backup.type === 'lxc' ? 'LXC' : 'Unknown',
        });
      }

      // Check for host backups - VMID 0 or type 'host' (for PMG/PVE host configs)
      if (isVmidZero || backup.type === 'host') {
        displayType = 'Host';
      } else if (backup.type === 'qemu' || backup.type === 'vm') {
        displayType = 'VM';
      } else if (backup.type === 'lxc' || backup.type === 'ct') {
        displayType = 'LXC';
      } else {
        // Default fallback
        displayType = 'LXC';
      }

      // For PBS backups through storage: show Proxmox node in Node column, PBS storage in Location
      // For regular backups: show Proxmox node in Node column, local storage in Location
      unified.push({
        backupType: backupType,
        vmid: displayType === 'Host' ? backup.vmid : backup.vmid || 0,
        name: backup.notes || backup.volid?.split('/').pop() || '',
        type: displayType,
        node: backup.node || '', // Proxmox node that has access to this backup
        instance: backup.instance || '', // Unique instance identifier
        backupTime: backup.ctime || 0,
        backupName: backup.volid?.split('/').pop() || '',
        description: backup.notes || '', // Use notes field for PBS backup descriptions
        status: backup.verified ? 'verified' : 'unverified',
        size: backup.size || null,
        storage: backup.storage || null, // Storage name (PBS storage or local storage)
        datastore: null, // Only set for direct PBS API backups
        namespace: null, // Only set for direct PBS API backups
        verified: backup.verified || false,
        protected: backup.protected || false,
        encrypted: backup.encryption ? true : false, // Check encryption field from Proxmox API
      });
    });

    return unified;
  });

  // Check if there are any Host type backups
  const hasHostBackups = createMemo(() => {
    const data = normalizedData();
    return data.some((backup) => backup.type === 'Host');
  });

  const hasAnyBackups = createMemo(() => normalizedData().length > 0);

  // Apply filters
  const filteredData = createMemo(() => {
    let data = normalizedData();
    const search = searchTerm().toLowerCase();
    const type = typeFilter();
    const backupType = backupTypeFilter();
    const dateRange = selectedDateRange();
    const nodeFilter = selectedNode();

    // Date range filter
    if (dateRange) {
      data = data.filter(
        (item) => item.backupTime >= dateRange.start && item.backupTime <= dateRange.end,
      );
    }

    // Node selection filter using both instance and node name for uniqueness
    if (nodeFilter) {
      const node = state.nodes?.find((n) => n.id === nodeFilter);
      if (node) {
        data = data.filter((item) => item.instance === node.instance && item.node === node.name);
      }
    }

    // Search filter - with advanced filtering support like Dashboard
    if (search) {
      // Check for special PBS namespace filter format first
      if (search.startsWith('pbs:')) {
        const parts = search.split(':');
        if (parts.length >= 4) {
          // Format: pbs:instanceName:datastoreName:namespace
          const [, instanceName, datastoreName, ...namespaceParts] = parts;
          const namespace = namespaceParts.join(':'); // Handle namespaces with colons

          data = data.filter((item) => {
            // Only PBS backups
            if (item.backupType !== 'remote') return false;
            // Match instance
            if (item.node !== instanceName) return false;
            // Match datastore
            if (item.datastore !== datastoreName) return false;
            // Match namespace (root namespace is represented as '/' or empty)
            const itemNamespace = item.namespace || '/';
            const searchNamespace = namespace || '/';
            return itemNamespace === searchNamespace;
          });
        }
      } else {
        // Split by commas first
        const searchParts = search
          .split(',')
          .map((t) => t.trim())
          .filter((t) => t);

        // Separate filters from text searches
        const filters: string[] = [];
        const textSearches: string[] = [];

        searchParts.forEach((part) => {
          if (part.includes('>') || part.includes('<') || part.includes(':')) {
            filters.push(part);
          } else {
            textSearches.push(part.toLowerCase());
          }
        });

        // Apply filters if any
        if (filters.length > 0) {
          // Join filters with AND operator
          const filterString = filters.join(' AND ');
          const stack = parseFilterStack(filterString);
          if (stack.filters.length > 0) {
            data = data.filter((item) => evaluateFilterStack(item, stack));
          }
        }

        // Apply text search if any
        if (textSearches.length > 0) {
          data = data.filter((item) =>
            textSearches.some((term) => {
              const searchFields = [
                item.vmid?.toString(),
                item.name,
                item.node,
                item.backupName,
                item.description,
                item.storage,
                item.datastore,
                item.namespace,
              ]
                .filter(Boolean)
                .map((field) => field!.toString().toLowerCase());

              return searchFields.some((field) => field.includes(term));
            }),
          );
        }
      }
    }

    // Type filter
    if (type !== 'all') {
      data = data.filter((item) => item.type === type);
    }

    // Backup type filter
    if (backupType !== 'all') {
      data = data.filter((item) => item.backupType === backupType);
    }

    // Sort
    const key = sortKey();
    const dir = sortDirection();
    data = [...data].sort((a, b) => {
      let aVal = a[key];
      let bVal = b[key];

      // Handle null/undefined/empty values - put at end for both asc and desc
      const aIsEmpty = aVal === null || aVal === undefined || aVal === '';
      const bIsEmpty = bVal === null || bVal === undefined || bVal === '';

      if (aIsEmpty && bIsEmpty) return 0;
      if (aIsEmpty) return 1;
      if (bIsEmpty) return -1;

      // Type-specific value preparation
      if (key === 'size' || key === 'vmid' || key === 'backupTime') {
        // Ensure numeric comparison
        aVal = typeof aVal === 'number' ? aVal : Number(aVal) || 0;
        bVal = typeof bVal === 'number' ? bVal : Number(bVal) || 0;
      }

      // Type-safe comparison
      if (typeof aVal === 'number' && typeof bVal === 'number') {
        if (aVal === bVal) return 0;
        const comparison = aVal < bVal ? -1 : 1;
        return dir === 'asc' ? comparison : -comparison;
      } else {
        // String comparison (case-insensitive)
        const aStr = String(aVal).toLowerCase();
        const bStr = String(bVal).toLowerCase();

        if (aStr === bStr) return 0;
        const comparison = aStr < bStr ? -1 : 1;
        return dir === 'asc' ? comparison : -comparison;
      }
    });

    return data;
  });

  // Group by date or guest
  const groupedData = createMemo(() => {
    const mode = groupByMode();
    const data = filteredData();

    // Group by guest mode
    if (mode === 'guest') {
      const groups: DateGroup[] = [];
      const groupMap = new Map<string, UnifiedBackup[]>();

      // Group by VMID and name combination
      data.forEach((item) => {
        // Create a unique key for each guest
        const guestKey = `${item.type} ${item.vmid}${item.name ? ` - ${item.name}` : ''}`;

        if (!groupMap.has(guestKey)) {
          groupMap.set(guestKey, []);
        }
        groupMap.get(guestKey)!.push(item);
      });

      // Convert to array and sort by VMID
      groupMap.forEach((items, label) => {
        groups.push({ label, items });
      });

      // Sort groups by VMID (extract from label)
      groups.sort((a, b) => {
        const vmidA = parseInt(a.label.match(/\d+/)?.[0] || '0');
        const vmidB = parseInt(b.label.match(/\d+/)?.[0] || '0');
        return vmidA - vmidB;
      });

      return groups;
    }

    // Group by date mode (default)
    // If not sorting by time, show all items in a single group to preserve sort order
    if (sortKey() !== 'backupTime') {
      return [
        {
          label: 'All Backups',
          items: data,
        },
      ];
    }

    const groups: DateGroup[] = [];
    const groupMap = new Map<string, UnifiedBackup[]>();
    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);

    const months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ];

    data.forEach((item) => {
      const date = new Date(item.backupTime * 1000);
      const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());

      let label: string;
      const month = months[date.getMonth()];
      const day = date.getDate();
      const suffix = getDaySuffix(day);
      const absoluteDate = `${month} ${day}${suffix}`;

      if (dateOnly.getTime() === today.getTime()) {
        label = `Today (${absoluteDate})`;
      } else if (dateOnly.getTime() === yesterday.getTime()) {
        label = `Yesterday (${absoluteDate})`;
      } else {
        label = absoluteDate;
      }

      if (!groupMap.has(label)) {
        groupMap.set(label, []);
      }
      groupMap.get(label)!.push(item);
    });

    // Convert to array
    groupMap.forEach((items, label) => {
      groups.push({ label, items });
    });

    // Sort groups based on sort direction
    if (sortDirection() === 'desc') {
      // Most recent first
      groups.sort((a, b) => {
        if (a.label.includes('Today')) return -1;
        if (b.label.includes('Today')) return 1;
        if (a.label.includes('Yesterday')) return b.label.includes('Today') ? 1 : -1;
        if (b.label.includes('Yesterday')) return a.label.includes('Today') ? -1 : 1;

        // For other dates, use the first item's date
        const dateA = a.items[0]?.backupTime || 0;
        const dateB = b.items[0]?.backupTime || 0;
        return dateB - dateA;
      });
    } else {
      // Oldest first
      groups.sort((a, b) => {
        if (a.label.includes('Today')) return 1;
        if (b.label.includes('Today')) return -1;
        if (a.label.includes('Yesterday')) return a.label.includes('Today') ? -1 : 1;
        if (b.label.includes('Yesterday')) return b.label.includes('Today') ? 1 : -1;

        // For other dates, use the first item's date
        const dateA = a.items[0]?.backupTime || 0;
        const dateB = b.items[0]?.backupTime || 0;
        return dateA - dateB;
      });
    }

    // Sort items within each group by time (already sorted by filteredData, but we need to maintain it)
    // The items come pre-sorted from filteredData(), so we don't need to re-sort them

    return groups;
  });

  // Sort handler
  const handleSort = (key: BackupSortKey) => {
    if (sortKey() === key) {
      // Toggle direction for the same column
      const newDir = sortDirection() === 'asc' ? 'desc' : 'asc';
      setSortDirection(newDir);
    } else {
      // New column - set key and default direction
      setSortKey(key);
      // Set default sort direction based on column type
      // For time and size, default to descending (newest/largest first)
      // For others, default to ascending
      if (key === 'backupTime' || key === 'size') {
        setSortDirection('desc');
      } else {
        setSortDirection('asc');
      }
    }
  };

  // Reset filters
  const resetFilters = () => {
    setSearchTerm('');
    setSelectedNode(null);
    setIsSearchLocked(false);
    setTypeFilter('all');
    setBackupTypeFilter('all');
    setGroupByMode('date');
    setSortKey('backupTime');
    setSortDirection('desc');
    setSelectedDateRange(null);
    setChartTimeRange(30);
  };

  // localStorage persistence is now handled by createLocalStorageBooleanSignal

  // Handle keyboard shortcuts
  let searchInputRef: HTMLInputElement | undefined;

  createEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      // Ignore if user is typing in an input, textarea, or contenteditable
      const target = e.target as HTMLElement;
      const isInputField =
        target.tagName === 'INPUT' ||
        target.tagName === 'TEXTAREA' ||
        target.tagName === 'SELECT' ||
        target.contentEditable === 'true';

      // Escape key behavior
      if (e.key === 'Escape') {
        // Clear search and reset filters
        if (
          searchTerm().trim() ||
          selectedNode() ||
          typeFilter() !== 'all' ||
          backupTypeFilter() !== 'all' ||
          selectedDateRange() !== null ||
          sortKey() !== 'backupTime' ||
          sortDirection() !== 'desc'
        ) {
          resetFilters();

          // Blur the search input if it's focused
          if (searchInputRef && document.activeElement === searchInputRef) {
            searchInputRef.blur();
          }
        }
      } else if (!isInputField && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
        // If it's a printable character and user is not in an input field
        // Focus the search input and let the character be typed
        if (searchInputRef) {
          searchInputRef.focus();
        }
      }
    };

    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  });

  // Get age color class
  const getAgeColorClass = (timestamp: number) => {
    if (!timestamp) return 'text-gray-500 dark:text-gray-400';

    const now = Date.now() / 1000;
    const diff = now - timestamp;
    const days = diff / 86400;

    if (days < 3) return 'text-green-600 dark:text-green-400';
    if (days < 7) return 'text-yellow-600 dark:text-yellow-400';
    if (days < 30) return 'text-orange-500 dark:text-orange-400';
    return 'text-red-600 dark:text-red-400';
  };

  // Get size color class
  const getSizeColor = (size: number | null) => {
    if (!size) return '';
    const gb = size / (1024 * 1024 * 1024);
    if (gb < 5) return 'text-green-600 dark:text-green-400';
    if (gb < 20) return 'text-yellow-600 dark:text-yellow-400';
    if (gb < 50) return 'text-orange-600 dark:text-orange-400';
    return 'text-red-600 dark:text-red-400';
  };

  // Calculate deduplication factor for PBS backups
  const dedupFactor = createMemo(() => {
    // Get all PBS instances with datastores
    if (!state.pbs || state.pbs.length === 0) return null;

    // Collect all deduplication factors from all datastores
    const dedupFactors: number[] = [];
    state.pbs.forEach((instance) => {
      if (instance.datastores) {
        instance.datastores.forEach((ds) => {
          if (ds.deduplicationFactor && ds.deduplicationFactor > 0) {
            dedupFactors.push(ds.deduplicationFactor);
          }
        });
      }
    });

    if (dedupFactors.length === 0) return null;

    // Calculate average deduplication factor across all datastores
    const avgFactor = dedupFactors.reduce((sum, f) => sum + f, 0) / dedupFactors.length;

    // Format as multiplication factor
    return avgFactor.toFixed(1) + 'x';
  });

  const parseDateKey = (dateKey: string) => {
    const [yearStr, monthStr, dayStr] = dateKey.split('-');
    const year = Number(yearStr);
    const month = Number(monthStr);
    const day = Number(dayStr);
    if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
      return new Date(dateKey);
    }
    return new Date(year, Math.max(0, month - 1), Math.max(1, day));
  };

  // Calculate available backup counts for chart
  const chartData = createMemo(() => {
    const days = chartTimeRange();
    const now = new Date();

    // Initialize data structure for each day
    const dailyData: {
      [key: string]: { snapshots: number; pve: number; pbs: number; total: number };
    } = {};

    // Create entries for each day in the range, including today
    for (let i = days - 1; i >= 0; i--) {
      const date = new Date(now);
      date.setDate(date.getDate() - i);
      // Use local date string format (YYYY-MM-DD) instead of ISO to avoid timezone issues
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      const dateKey = `${year}-${month}-${day}`;
      dailyData[dateKey] = { snapshots: 0, pve: 0, pbs: 0, total: 0 };
    }

    // Calculate the actual start and end times for filtering
    const startDate = new Date(now);
    startDate.setDate(startDate.getDate() - (days - 1));
    startDate.setHours(0, 0, 0, 0);
    const startTime = startDate.getTime();

    const endDate = new Date(now);
    endDate.setHours(23, 59, 59, 999);
    const endTime = endDate.getTime();

    // Use filtered data but WITHOUT date range filter for the chart
    // The chart should show the time range, and filters should affect what's counted
    let dataForChart = normalizedData();
    const search = searchTerm().toLowerCase();
    const type = typeFilter();
    const backupType = backupTypeFilter();

    // Apply search filter - with advanced filtering support like the table
    if (search) {
      // Check for special PBS namespace filter first
      if (search.startsWith('pbs:')) {
        const parts = search.split(':');
        if (parts.length >= 4) {
          // Format: pbs:instanceName:datastoreName:namespace
          const [, instanceName, datastoreName, ...namespaceParts] = parts;
          const namespace = namespaceParts.join(':'); // Handle namespaces with colons

          dataForChart = dataForChart.filter((item) => {
            // Only PBS backups
            if (item.backupType !== 'remote') return false;
            // Match instance
            if (item.node !== instanceName) return false;
            // Match datastore
            if (item.datastore !== datastoreName) return false;
            // Match namespace (root namespace is represented as '/' or empty)
            const itemNamespace = item.namespace || '/';
            const searchNamespace = namespace || '/';
            return itemNamespace === searchNamespace;
          });
        }
      } else {
        // Split by commas first
        const searchParts = search
          .split(',')
          .map((t) => t.trim())
          .filter((t) => t);

        // Separate filters from text searches
        const filters: string[] = [];
        const textSearches: string[] = [];

        searchParts.forEach((part) => {
          if (part.includes('>') || part.includes('<') || part.includes(':')) {
            filters.push(part);
          } else {
            textSearches.push(part.toLowerCase());
          }
        });

        // Apply filters if any
        if (filters.length > 0) {
          // Join filters with AND operator
          const filterString = filters.join(' AND ');
          const stack = parseFilterStack(filterString);
          if (stack.filters.length > 0) {
            dataForChart = dataForChart.filter((item) => evaluateFilterStack(item, stack));
          }
        }

        // Apply text search if any
        if (textSearches.length > 0) {
          dataForChart = dataForChart.filter((item) =>
            textSearches.some((term) => {
              const searchFields = [
                item.vmid?.toString(),
                item.name,
                item.node,
                item.backupName,
                item.description,
                item.storage,
                item.datastore,
                item.namespace,
              ]
                .filter(Boolean)
                .map((field) => field!.toString().toLowerCase());

              return searchFields.some((field) => field.includes(term));
            }),
          );
        }
      }
    }

    // Apply type filter
    if (type !== 'all') {
      dataForChart = dataForChart.filter((item) => item.type === type);
    }

    // Apply backup type filter
    if (backupType !== 'all') {
      dataForChart = dataForChart.filter((item) => item.backupType === backupType);
    }

    // Count backups per day within the chart time range
    dataForChart.forEach((backup) => {
      const backupTime = backup.backupTime * 1000;
      if (backupTime >= startTime && backupTime <= endTime) {
        const date = new Date(backupTime);
        // Use local date string format (YYYY-MM-DD) to match the keys we created
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const dateKey = `${year}-${month}-${day}`;

        if (dailyData[dateKey]) {
          dailyData[dateKey].total++;
          if (backup.backupType === 'snapshot') {
            dailyData[dateKey].snapshots++;
          } else if (backup.backupType === 'local') {
            dailyData[dateKey].pve++;
          } else if (backup.backupType === 'remote') {
            dailyData[dateKey].pbs++;
          }
        }
      }
    });

    // Convert to array and calculate max value for scaling
    const dataArray = Object.entries(dailyData).map(([date, counts]) => ({
      date,
      ...counts,
    }));

    const maxValue = Math.max(...dataArray.map((d) => d.total), 1);

    return { data: dataArray, maxValue };
  });

  // Sort PBS instances by status then by name
  const sortedPBSInstances = createMemo(() => {
    if (!state.pbs) return [];
    return [...state.pbs].sort((a, b) => {
      // Online instances first
      const aOnline = a.status === 'healthy' || a.status === 'online';
      const bOnline = b.status === 'healthy' || b.status === 'online';
      if (aOnline !== bOnline) return aOnline ? -1 : 1;
      // Then by name
      return a.name.localeCompare(b.name);
    });
  });

  return (
    <div class="space-y-4">
      {/* Empty State - No nodes at all configured */}
      <Show
        when={
          !isLoading() && (state.nodes || []).length === 0 && (!state.pbs || state.pbs.length === 0)
        }
      >
        <Card padding="lg">
          <EmptyState
            icon={
              <svg
                class="h-12 w-12 text-gray-400"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
                />
              </svg>
            }
            title="No backup sources configured"
            description="Add a Proxmox VE or PBS node in the Settings tab to start monitoring backups."
            actions={
              <button
                type="button"
                onClick={() => navigate('/settings')}
                class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
              >
                Go to Settings
              </button>
            }
          />
        </Card>
      </Show>

      {/* Unified Node Selector */}
      <UnifiedNodeSelector
        currentTab="backups"
        globalTemperatureMonitoringEnabled={state.temperatureMonitoringEnabled}
        onNodeSelect={(nodeId) => {
          setSelectedNode(nodeId);
        }}
        onNamespaceSelect={(namespaceFilter) => {
          setSearchTerm(namespaceFilter);
          // Only lock if we're setting a filter, unlock if clearing
          setIsSearchLocked(namespaceFilter !== '');
        }}
        searchTerm={searchTerm()}
      />

      {/* Removed old PBS table */}
      <Show when={false && sortedPBSInstances().length > 0}>
        <Card padding="none" class="overflow-hidden">
          <div class="overflow-x-auto" style="scrollbar-width: none; -ms-overflow-style: none;">
            <style>{`
              .overflow-x-auto::-webkit-scrollbar { display: none; }
            `}</style>
            <table class="w-full">
              <thead>
                <tr class="border-b border-gray-200 dark:border-gray-700">
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    PBS Instance
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Status
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    CPU
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Memory
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Storage
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Datastores
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Backups
                  </th>
                  <th class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider">
                    Uptime
                  </th>
                </tr>
              </thead>
              <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
                <For each={sortedPBSInstances()}>
                  {(pbs) => {
                    const isOnline = () => pbs.status === 'healthy' || pbs.status === 'online';
                    const cpuPercent = () => Math.round(pbs.cpu || 0);
                    const memPercent = () =>
                      pbs.memoryTotal ? Math.round((pbs.memoryUsed / pbs.memoryTotal) * 100) : 0;

                    // Calculate total storage across all datastores
                    const totalStorage = () => {
                      if (!pbs.datastores) return { used: 0, total: 0, percent: 0 };
                      const totals = pbs.datastores.reduce(
                        (acc, ds) => {
                          acc.used += ds.used || 0;
                          acc.total += ds.total || 0;
                          return acc;
                        },
                        { used: 0, total: 0 },
                      );
                      return {
                        ...totals,
                        percent:
                          totals.total > 0 ? Math.round((totals.used / totals.total) * 100) : 0,
                      };
                    };

                    const storage = totalStorage();

                    // Count backups for this PBS instance
                    const pbsBackups = () =>
                      pbsBackupsState()?.filter((b) => b.instance === pbs.name).length || 0;

                    const isSelected = () => selectedPBSInstance() === pbs.name;

                    return (
                      <tr
                        class={`hover:bg-gray-50 dark:hover:bg-gray-700/30 cursor-pointer transition-colors ${
                          isSelected() ? 'bg-blue-50 dark:bg-blue-900/20' : ''
                        }`}
                        onClick={() => {
                          const currentSearch = searchTerm();
                          const nodeFilter = `node:${pbs.name}`;

                          if (currentSearch.includes(nodeFilter)) {
                            setSearchTerm(
                              currentSearch
                                .replace(nodeFilter, '')
                                .trim()
                                .replace(/,\s*,/g, ',')
                                .replace(/^,|,$/g, ''),
                            );
                            setIsSearchLocked(false);
                          } else {
                            const cleanedSearch = currentSearch
                              .replace(/node:\S+/g, '')
                              .trim()
                              .replace(/,\s*,/g, ',')
                              .replace(/^,|,$/g, '');
                            const newSearch = cleanedSearch
                              ? `${cleanedSearch}, ${nodeFilter}`
                              : nodeFilter;
                            setSearchTerm(newSearch);
                            setIsSearchLocked(true);
                          }
                        }}
                      >
                        <td class="p-0.5 px-1.5 whitespace-nowrap">
                          <div class="flex items-center gap-1">
                            <a
                              href={pbs.host || `https://${pbs.name}:8007`}
                              target="_blank"
                              onClick={(e) => e.stopPropagation()}
                              class="font-medium text-xs text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400"
                            >
                              {pbs.name}
                            </a>
                            <Show when={pbs.version}>
                              <span class="text-[9px] text-gray-500 dark:text-gray-400">
                                v{pbs.version}
                              </span>
                            </Show>
                          </div>
                        </td>
                        <td class="p-0.5 px-1.5 whitespace-nowrap">
                          <div class="flex items-center gap-1">
                            <span
                              class={`h-2 w-2 rounded-full ${
                                isOnline() ? 'bg-green-500' : 'bg-red-500'
                              }`}
                            />
                            <span class="text-xs text-gray-600 dark:text-gray-400">
                              {isOnline() ? 'Online' : 'Offline'}
                            </span>
                          </div>
                        </td>
                        <td class="p-0.5 px-1.5 min-w-[180px]">
                          <MetricBar value={cpuPercent()} label={formatPercent(cpuPercent())} type="cpu" />
                        </td>
                        <td class="p-0.5 px-1.5 min-w-[180px]">
                          <MetricBar
                            value={memPercent()}
                            label={formatPercent(memPercent())}
                            sublabel={
                              pbs.memoryTotal
                                ? `${formatBytes(pbs.memoryUsed, 0)}/${formatBytes(pbs.memoryTotal, 0)}`
                                : undefined
                            }
                            type="memory"
                          />
                        </td>
                        <td class="p-0.5 px-1.5 min-w-[180px]">
                          <MetricBar
                            value={storage.percent}
                            label={formatPercent(storage.percent)}
                            sublabel={`${formatBytes(storage.used, 0)}/${formatBytes(storage.total, 0)}`}
                            type="disk"
                          />
                        </td>
                        <td class="p-0.5 px-1.5 whitespace-nowrap text-center">
                          <span class="text-xs text-gray-700 dark:text-gray-300">
                            {pbs.datastores?.length || 0}
                          </span>
                        </td>
                        <td class="p-0.5 px-1.5 whitespace-nowrap text-center">
                          <span class="text-xs text-gray-700 dark:text-gray-300">
                            {pbsBackups()}
                          </span>
                        </td>
                        <td class="p-0.5 px-1.5 whitespace-nowrap">
                          <span class="text-xs text-gray-600 dark:text-gray-400">
                            <Show when={isOnline() && pbs.uptime} fallback="-">
                              {formatUptime(pbs.uptime)}
                            </Show>
                          </span>
                        </td>
                      </tr>
                    );
                  }}
                </For>
              </tbody>
            </table>
          </div>
        </Card>
      </Show>

      {/* Main Content - show when any nodes or PBS are configured */}
      <Show when={(state.nodes || []).length > 0 || (state.pbs && state.pbs.length > 0)}>
        {/* Available Backups chart - keep visible while backups exist or a date filter is active */}
        <Show when={hasAnyBackups() || selectedDateRange() !== null}>
          <Card padding="md">
            <div class="mb-3 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
              <SectionHeader
                title={
                  <div class="flex items-center gap-1.5">
                    <span>Available backups</span>
                    <button
                      type="button"
                      class="text-gray-400 hover:text-gray-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 dark:text-gray-500 dark:hover:text-gray-300"
                      aria-label={availableBackupsTooltipText}
                      onMouseEnter={(e) => {
                        const rect = e.currentTarget.getBoundingClientRect();
                        showTooltip(
                          availableBackupsTooltipText,
                          rect.left + rect.width / 2,
                          rect.top,
                          {
                            align: 'center',
                            direction: 'up',
                          },
                        );
                      }}
                      onFocus={(e) => {
                        const rect = e.currentTarget.getBoundingClientRect();
                        showTooltip(
                          availableBackupsTooltipText,
                          rect.left + rect.width / 2,
                          rect.top,
                          {
                            align: 'center',
                            direction: 'up',
                          },
                        );
                      }}
                      onMouseLeave={() => hideTooltip()}
                      onBlur={() => hideTooltip()}
                    >
                      <svg
                        class="h-4 w-4"
                        fill="none"
                        stroke="currentColor"
                        stroke-width="1.6"
                        viewBox="0 0 24 24"
                        aria-hidden="true"
                      >
                        <path
                          stroke-linecap="round"
                          stroke-linejoin="round"
                          d="M11.25 11.25h1.5v3.75m-.75-9.75h.008v.008H12.75V5.25H11.25Zm0 0a7.5 7.5 0 1 1-7.5 7.5 7.5 7.5 0 0 1 7.5-7.5Z"
                        />
                      </svg>
                    </button>
                  </div>
                }
                size="sm"
                class="flex-1"
              />
              <div class="flex flex-col items-start gap-2 sm:items-end">
                <Show when={selectedDateRange()}>
                  {(selection) => (
                    <div class="inline-flex items-center gap-2 rounded-full border border-blue-200 dark:border-blue-700 bg-blue-50 dark:bg-blue-900/30 px-3 py-1 text-xs text-blue-700 dark:text-blue-200">
                      <span class="font-medium uppercase tracking-wide text-[10px] text-blue-600 dark:text-blue-300">
                        Filtered Date
                      </span>
                      <span class="font-mono text-[11px]">
                        {new Date(selection().start * 1000).toLocaleDateString('en-US', {
                          month: 'short',
                          day: 'numeric',
                          year: 'numeric',
                        })}
                      </span>
                    </div>
                  )}
                </Show>
                <div class="flex flex-wrap items-center justify-start gap-2 sm:justify-end">
                  <div class="flex items-center gap-1">
                    <button
                      type="button"
                      onClick={() => setChartTimeRange(7)}
                      class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
                        chartTimeRange() === 7
                          ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
                          : 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
                      }`}
                    >
                      7d
                    </button>
                    <button
                      type="button"
                      onClick={() => setChartTimeRange(30)}
                      class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
                        chartTimeRange() === 30
                          ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
                          : 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
                      }`}
                    >
                      30d
                    </button>
                    <button
                      type="button"
                      onClick={() => setChartTimeRange(90)}
                      class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
                        chartTimeRange() === 90
                          ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
                          : 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
                      }`}
                    >
                      90d
                    </button>
                    <button
                      type="button"
                      onClick={() => setChartTimeRange(365)}
                      class={`p-0.5 px-1.5 text-xs border rounded transition-colors ${
                        chartTimeRange() === 365
                          ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700'
                          : 'border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
                      }`}
                    >
                      1y
                    </button>
                  </div>
                  <div class="hidden h-4 w-px bg-gray-300 dark:bg-gray-600 sm:block"></div>
                  <span class="text-gray-500 dark:text-gray-400">Last {chartTimeRange()} days</span>
                  <Show when={selectedDateRange()}>
                    <button
                      type="button"
                      onClick={() => {
                        setSelectedDateRange(null);
                        hideTooltip();
                      }}
                      class="p-0.5 px-1.5 text-xs bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800/50 transition-colors"
                    >
                      Clear filter
                    </button>
                  </Show>
                </div>
              </div>
            </div>
            <div class="relative h-32 bg-gray-100 dark:bg-gray-800 rounded overflow-hidden">
              <Show
                when={chartData().data.length > 0}
                fallback={
                  <div class="flex h-full items-center justify-center">
                    <EmptyState
                      class="max-w-xs"
                      align="center"
                      icon={
                        <svg
                          class="h-10 w-10 text-gray-400"
                          fill="none"
                          viewBox="0 0 24 24"
                          stroke="currentColor"
                        >
                          <path
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            stroke-width="2"
                            d="M4 19h16M7 10h2v9H7zm4-5h2v14h-2zm4 8h2v6h-2z"
                          />
                        </svg>
                      }
                      title="No backup data"
                      description="Adjust filters or expand the time range to see activity."
                    />
                  </div>
                }
              >
                <svg
                  class="available-backups-svg w-full h-full"
                  role="img"
                  aria-label="Available backups chart"
                  style="cursor: pointer; touch-action: pan-y;"
                  ref={(el) => {
                    // Use createEffect to reactively update the chart
                    createEffect(() => {
                      if (!el) return;

                      const data = chartData().data;
                      if (data.length === 0) return;

                      // Wait for next frame to ensure dimensions are available
                      requestAnimationFrame(() => {
                        const rect = el.getBoundingClientRect();
                        if (rect.width === 0 || rect.height === 0) return;

                        const margin = { top: 10, right: 10, bottom: 30, left: 30 };
                        const width = Math.max(rect.width - margin.left - margin.right, 1);
                        const height = 128 - margin.top - margin.bottom;

                        el.setAttribute('viewBox', `0 0 ${rect.width} 128`);
                        // Clear existing content safely
                        while (el.firstChild) {
                          el.removeChild(el.firstChild);
                        }

                        // Create main group
                        const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
                        g.setAttribute('transform', `translate(${margin.left},${margin.top})`);
                        el.appendChild(g);

                        const data = chartData().data;
                        const maxValue = chartData().maxValue;
                        const xScale = width / Math.max(data.length, 1);
                        const barWidth = Math.max(1, Math.min(xScale - 2, 50));
                        const yScale = height / maxValue;

                        // Add grid lines
                        const gridGroup = document.createElementNS(
                          'http://www.w3.org/2000/svg',
                          'g',
                        );
                        gridGroup.setAttribute('class', 'grid-lines');
                        g.appendChild(gridGroup);

                        // Y-axis grid lines
                        const gridCount = 5;
                        for (let i = 0; i <= gridCount; i++) {
                          const y = height - (i * height) / gridCount;
                          const line = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            'line',
                          );
                          line.setAttribute('x1', '0');
                          line.setAttribute('y1', y.toString());
                          line.setAttribute('x2', width.toString());
                          line.setAttribute('y2', y.toString());
                          line.setAttribute('stroke', 'currentColor');
                          line.setAttribute('stroke-opacity', '0.1');
                          line.setAttribute('class', 'text-gray-300 dark:text-gray-600');
                          gridGroup.appendChild(line);
                        }

                        // Add Y-axis labels
                        if (maxValue <= 5) {
                          for (let i = 0; i <= maxValue; i++) {
                            const y = height - (i * height) / maxValue;
                            const text = document.createElementNS(
                              'http://www.w3.org/2000/svg',
                              'text',
                            );
                            text.setAttribute('x', '-5');
                            text.setAttribute('y', (y + 3).toString());
                            text.setAttribute('text-anchor', 'end');
                            text.setAttribute(
                              'class',
                              'text-[10px] fill-gray-500 dark:fill-gray-400',
                            );
                            text.textContent = i.toString();
                            g.appendChild(text);
                          }
                        } else {
                          for (let i = 0; i <= gridCount; i++) {
                            const value = Math.round((i * maxValue) / gridCount);
                            const y = height - (i * height) / gridCount;

                            if (i === 0 || value !== Math.round(((i - 1) * maxValue) / gridCount)) {
                              const text = document.createElementNS(
                                'http://www.w3.org/2000/svg',
                                'text',
                              );
                              text.setAttribute('x', '-5');
                              text.setAttribute('y', (y + 3).toString());
                              text.setAttribute('text-anchor', 'end');
                              text.setAttribute(
                                'class',
                                'text-[10px] fill-gray-500 dark:fill-gray-400',
                              );
                              text.textContent = value.toString();
                              g.appendChild(text);
                            }
                          }
                        }

                        // Add bars
                        const barsGroup = document.createElementNS(
                          'http://www.w3.org/2000/svg',
                          'g',
                        );
                        barsGroup.setAttribute('class', 'bars');
                        g.appendChild(barsGroup);

                        data.forEach((d, i) => {
                          const barHeight = d.total * yScale;
                          const x = Math.max(0, i * xScale + (xScale - barWidth) / 2);
                          const y = height - barHeight;
                          const barDate = parseDateKey(d.date);
                          const barDateMs = barDate.getTime();

                          const barGroup = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            'g',
                          );
                          barGroup.setAttribute('class', 'bar-group');
                          barGroup.setAttribute('data-date', d.date);
                          barGroup.style.cursor = 'pointer';

                          // Background track for all slots
                          const track = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            'rect',
                          );
                          track.setAttribute('x', x.toString());
                          track.setAttribute('y', (height - 2).toString());
                          track.setAttribute('width', barWidth.toString());
                          track.setAttribute('height', '2');
                          track.setAttribute('rx', '1');
                          track.setAttribute('fill', '#d1d5db');
                          track.setAttribute('fill-opacity', '0.3');
                          barGroup.appendChild(track);

                          // Click area
                          const clickRect = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            'rect',
                          );
                          clickRect.setAttribute('x', (i * xScale).toString());
                          clickRect.setAttribute('y', '0');
                          clickRect.setAttribute('width', Math.max(1, xScale).toString());
                          clickRect.setAttribute('height', height.toString());
                          clickRect.setAttribute('fill', 'transparent');
                          clickRect.style.cursor = 'pointer';
                          barGroup.appendChild(clickRect);

                          // Main bar
                          const rect = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            'rect',
                          );
                          rect.setAttribute('x', x.toString());
                          rect.setAttribute('y', y.toString());
                          rect.setAttribute('width', barWidth.toString());
                          rect.setAttribute('height', barHeight.toString());
                          rect.setAttribute('rx', '2');
                          rect.setAttribute('class', 'backup-bar');
                          rect.setAttribute('data-date', d.date);

                          // Color based on count
                          let barColor = '#e5e7eb';
                          if (d.total > 0 && d.total <= 5) barColor = '#60a5fa';
                          else if (d.total <= 10) barColor = '#34d399';
                          else if (d.total > 10) barColor = '#a78bfa';

                          rect.setAttribute('fill', barColor);
                          rect.style.transition = 'all 0.2s ease';

                          // Highlight selected date
                          const isSelected =
                            selectedDateRange() &&
                            barDateMs >= selectedDateRange()!.start * 1000 &&
                            barDateMs <= selectedDateRange()!.end * 1000;

                          if (isSelected) {
                            rect.setAttribute('fill-opacity', '1');
                            rect.setAttribute('stroke', '#2563eb');
                            rect.setAttribute('stroke-width', '2');
                            rect.setAttribute('filter', 'brightness(1.1)');
                          } else {
                            rect.setAttribute('fill-opacity', '0.8');
                            rect.setAttribute('stroke', 'none');
                          }

                          barGroup.appendChild(rect);

                          // Stacked segments
                          if (d.total > 0) {
                            // PBS (bottom)
                            if (d.pbs > 0) {
                              const pbsHeight = (d.pbs / d.total) * barHeight;
                              const pbsRect = document.createElementNS(
                                'http://www.w3.org/2000/svg',
                                'rect',
                              );
                              pbsRect.setAttribute('x', x.toString());
                              pbsRect.setAttribute('y', (y + barHeight - pbsHeight).toString());
                              pbsRect.setAttribute('width', barWidth.toString());
                              pbsRect.setAttribute('height', pbsHeight.toString());
                              pbsRect.setAttribute('rx', '2');
                              pbsRect.setAttribute('fill', '#8b5cf6');
                              pbsRect.setAttribute('fill-opacity', '0.9');
                              barGroup.appendChild(pbsRect);
                            }

                            // PVE (middle)
                            if (d.pve > 0) {
                              const pveHeight = (d.pve / d.total) * barHeight;
                              const pveY = y + (d.snapshots / d.total) * barHeight;
                              const pveRect = document.createElementNS(
                                'http://www.w3.org/2000/svg',
                                'rect',
                              );
                              pveRect.setAttribute('x', x.toString());
                              pveRect.setAttribute('y', pveY.toString());
                              pveRect.setAttribute('width', barWidth.toString());
                              pveRect.setAttribute('height', pveHeight.toString());
                              pveRect.setAttribute('fill', '#f97316');
                              pveRect.setAttribute('fill-opacity', '0.9');
                              barGroup.appendChild(pveRect);
                            }

                            // Snapshots (top)
                            if (d.snapshots > 0) {
                              const snapshotHeight = (d.snapshots / d.total) * barHeight;
                              const snapshotRect = document.createElementNS(
                                'http://www.w3.org/2000/svg',
                                'rect',
                              );
                              snapshotRect.setAttribute('x', x.toString());
                              snapshotRect.setAttribute('y', y.toString());
                              snapshotRect.setAttribute('width', barWidth.toString());
                              snapshotRect.setAttribute('height', snapshotHeight.toString());
                              snapshotRect.setAttribute('rx', '2');
                              snapshotRect.setAttribute('fill', '#eab308');
                              snapshotRect.setAttribute('fill-opacity', '0.9');
                              barGroup.appendChild(snapshotRect);
                            }
                          }

                          // Hover effects with tooltips
                          barGroup.addEventListener('mouseenter', (event) => {
                            const e = event as MouseEvent;
                            rect.setAttribute('fill-opacity', '1');
                            rect.setAttribute('filter', 'brightness(1.2)');

                            // Show tooltip
                            const formattedDate = barDate.toLocaleDateString('en-US', {
                              weekday: 'short',
                              month: 'short',
                              day: 'numeric',
                            });

                            let tooltipText = `${formattedDate}`;

                            if (d.total > 0) {
                              tooltipText += `\nAvailable: ${d.total} backup${
                                d.total > 1 ? 's' : ''
                              }`;

                              const breakdown: string[] = [];
                              if (d.snapshots > 0) breakdown.push(`Snapshots: ${d.snapshots}`);
                              if (d.pve > 0) breakdown.push(`PVE: ${d.pve}`);
                              if (d.pbs > 0) breakdown.push(`PBS: ${d.pbs}`);

                              if (breakdown.length > 0) {
                                tooltipText += `\n${breakdown.join(' • ')}`;
                              }
                            } else {
                              tooltipText += '\nNo backups available';
                            }

                            const mouseX = e.clientX;
                            const mouseY = e.clientY;

                            showTooltip(tooltipText, mouseX, mouseY, {
                              align: 'center',
                              direction: 'up',
                            });
                          });

                          barGroup.addEventListener('mouseleave', () => {
                            rect.setAttribute('fill-opacity', '0.8');
                            rect.removeAttribute('filter');
                            hideTooltip();
                          });

                          // Click to filter
                          barGroup.addEventListener('click', () => {
                            const startOfDay = new Date(barDate);
                            startOfDay.setHours(0, 0, 0, 0);
                            const endOfDay = new Date(barDate);
                            endOfDay.setHours(23, 59, 59, 999);
                            const clickedRange = {
                              start: startOfDay.getTime() / 1000,
                              end: endOfDay.getTime() / 1000,
                            };

                            const currentRange = selectedDateRange();
                            if (
                              currentRange &&
                              currentRange.start === clickedRange.start &&
                              currentRange.end === clickedRange.end
                            ) {
                              setSelectedDateRange(null);
                            } else {
                              setSelectedDateRange(clickedRange);
                            }

                            hideTooltip();
                          });

                          barsGroup.appendChild(barGroup);

                          // Date labels
                          let showLabel = false;
                          if (chartTimeRange() <= 7) {
                            showLabel = true;
                          } else if (chartTimeRange() <= 30) {
                            showLabel =
                              i % Math.ceil(data.length / 10) === 0 || i === data.length - 1;
                          } else if (chartTimeRange() <= 90) {
                            const dayOfWeek = barDate.getDay();
                            showLabel = dayOfWeek === 0 || i === 0 || i === data.length - 1;
                          } else {
                            showLabel = barDate.getDate() === 1 || i === 0 || i === data.length - 1;
                          }

                          if (showLabel) {
                            const text = document.createElementNS(
                              'http://www.w3.org/2000/svg',
                              'text',
                            );
                            text.setAttribute('x', (x + barWidth / 2).toString());
                            text.setAttribute('y', (height + 20).toString());
                            text.setAttribute('text-anchor', 'middle');
                            text.setAttribute(
                              'class',
                              'text-[8px] fill-gray-500 dark:fill-gray-400',
                            );

                            // Use shorter format for horizontal labels
                            let labelText;
                            if (chartTimeRange() <= 7) {
                              // For 7 days, show month/day
                              labelText = `${barDate.getMonth() + 1}/${barDate.getDate()}`;
                            } else if (chartTimeRange() <= 30) {
                              // For 30 days, show day only (or month/day for first of month)
                              labelText =
                                barDate.getDate() === 1
                                  ? `${barDate.getMonth() + 1}/1`
                                  : barDate.getDate().toString();
                            } else {
                              // For longer ranges, show month/day
                              labelText = `${barDate.getMonth() + 1}/${barDate.getDate()}`;
                            }
                            text.textContent = labelText;
                            g.appendChild(text);
                          }
                        });
                      });
                    });
                  }}
                />
              </Show>
            </div>
            <div class="flex justify-between items-center text-xs mt-2">
              <Show when={dedupFactor()}>
                <div class="flex items-center gap-1">
                  <span class="text-gray-500 dark:text-gray-400">Deduplication:</span>
                  <span class="font-medium text-green-600 dark:text-green-400">
                    {dedupFactor()}
                  </span>
                </div>
              </Show>
              <Show when={!dedupFactor()}>
                <div></div>
              </Show>
              <div class="flex items-center gap-3">
                <span class="flex items-center gap-1">
                  <span class="inline-block w-3 h-3 rounded bg-yellow-500"></span>
                  <span class="text-gray-600 dark:text-gray-400">Snapshots</span>
                </span>
                <span class="flex items-center gap-1">
                  <span class="inline-block w-3 h-3 rounded bg-orange-500"></span>
                  <span class="text-gray-600 dark:text-gray-400">PVE</span>
                </span>
                <span class="flex items-center gap-1">
                  <span class="inline-block w-3 h-3 rounded bg-violet-500"></span>
                  <span class="text-gray-600 dark:text-gray-400">PBS</span>
                </span>
              </div>
            </div>
          </Card>
        </Show>

        {/* Backups Filter */}
        <BackupsFilter
          search={searchTerm}
          setSearch={setSearchTerm}
          viewMode={uiBackupTypeFilter}
          setViewMode={setUiBackupTypeFilter}
          groupBy={groupByMode}
          setGroupBy={setGroupByMode}
          searchInputRef={(el) => (searchInputRef = el)}
          typeFilter={typeFilter}
          setTypeFilter={setTypeFilter}
          hasHostBackups={hasHostBackups}
          sortOptions={sortKeyOptions}
          sortKey={sortKey}
          setSortKey={(value) => setSortKey(value as BackupSortKey)}
          sortDirection={sortDirection}
          setSortDirection={setSortDirection}
          onReset={resetFilters}
        />

        {/* Table */}
        <Card padding="none" class="mb-4 overflow-hidden">
          <div class="overflow-x-auto" style="scrollbar-width: none; -ms-overflow-style: none;">
            <style>{`
          .overflow-x-auto::-webkit-scrollbar { display: none; }
          .backup-table {
            table-layout: fixed;
            width: 100%;
            min-width: 1200px;
          }
          .backup-table th,
          .backup-table td {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
          }
        `}</style>
            <Show
              when={!isLoading()}
              fallback={
                <Card padding="lg">
                  <EmptyState
                    icon={
                      <svg
                        class="h-12 w-12 animate-spin text-gray-400"
                        fill="none"
                        viewBox="0 0 24 24"
                      >
                        <circle
                          class="opacity-25"
                          cx="12"
                          cy="12"
                          r="10"
                          stroke="currentColor"
                          stroke-width="4"
                        />
                        <path
                          class="opacity-75"
                          fill="currentColor"
                          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                        />
                      </svg>
                    }
                    title="Loading backup data..."
                    description="This may take up to 20 seconds on the first load."
                  />
                </Card>
              }
            >
              <Show
                when={groupedData().length > 0}
                fallback={
                  <Card padding="lg">
                    <EmptyState
                      icon={
                        <svg
                          class="h-12 w-12 text-gray-400"
                          fill="none"
                          viewBox="0 0 24 24"
                          stroke="currentColor"
                        >
                          <path
                            stroke-linecap="round"
                            stroke-linejoin="round"
                            stroke-width="2"
                            d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
                          />
                        </svg>
                      }
                      title="No backups match your filters"
                      description="Try adjusting filters or selecting a different time range."
                    />
                  </Card>
                }
              >
                {/* Mobile Card View - Compact */}
                <div class="block lg:hidden space-y-3">
                  <For each={groupedData()}>
                    {(group) => (
                      <div class="space-y-1">
                        <div class="text-xs font-medium text-gray-600 dark:text-gray-400 px-2 py-1 sticky top-0 bg-gray-50 dark:bg-gray-900 z-10">
                          {group.label} ({group.items.length})
                        </div>
                        <For each={group.items}>
                          {(item) => (
                            <Card padding="sm" class="hover:shadow-sm transition-shadow">
                              {/* Compact header row */}
                              <div class="flex items-center justify-between gap-2 mb-1">
                                <div class="flex items-center gap-2 min-w-0 flex-1">
                                  <span
                                    class={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium shrink-0 ${
                                      item.type === 'VM'
                                        ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
                                        : 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
                                    }`}
                                  >
                                    {item.type}
                                  </span>
                                  <span class="text-xs text-gray-500 shrink-0">{item.vmid}</span>
                                  <span class="font-medium text-xs truncate">
                                    {item.name || 'Unnamed'}
                                  </span>
                                </div>
                                <div class="flex items-center gap-2 shrink-0">
                                  <span
                                    class={`inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${
                                      item.backupType === 'snapshot'
                                        ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'
                                        : item.backupType === 'local'
                                          ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
                                          : 'bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-200'
                                    }`}
                                  >
                                    {item.backupType === 'snapshot'
                                      ? 'SNAP'
                                      : item.backupType === 'local'
                                        ? 'PVE'
                                        : 'PBS'}
                                  </span>
                                </div>
                              </div>

                              {/* Compact info row */}
                              <div class="flex items-center justify-between gap-2 text-[11px]">
                                <div class="flex items-center gap-3 text-gray-600 dark:text-gray-400">
                                  <span>{item.node}</span>
                                  <span class={getAgeColorClass(item.backupTime)}>
                                    {formatTime(item.backupTime * 1000)}
                                  </span>
                                  <Show when={item.size}>
                                    <span class={getSizeColor(item.size)}>
                                      {formatBytes(item.size!)}
                                    </span>
                                  </Show>
                                  <Show when={item.backupType === 'remote' && item.verified}>
                                    <svg
                                      class="w-4 h-4 text-green-600 dark:text-green-400 inline"
                                      fill="none"
                                      viewBox="0 0 24 24"
                                      stroke="currentColor"
                                    >
                                      <path
                                        stroke-linecap="round"
                                        stroke-linejoin="round"
                                        stroke-width="2"
                                        d="M5 13l4 4L19 7"
                                      />
                                    </svg>
                                  </Show>
                                </div>
                                <Show
                                  when={
                                    (item.storage || item.datastore) &&
                                    item.backupType !== 'snapshot'
                                  }
                                >
                                  <span class="text-gray-500 dark:text-gray-400 text-[10px] truncate max-w-[100px]">
                                    {item.storage ||
                                      (item.datastore &&
                                        (item.namespace && item.namespace !== 'root'
                                          ? `${item.datastore}/${item.namespace}`
                                          : item.datastore)) ||
                                      '-'}
                                  </span>
                                </Show>
                              </div>
                            </Card>
                          )}
                        </For>
                      </div>
                    )}
                  </For>
                </div>

                {/* Desktop Table View */}
                <table class="backup-table hidden lg:table">
                  <thead>
                    <tr class="bg-gray-50 dark:bg-gray-700/50 text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-600">
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('vmid')}
                        style="width: 60px;"
                      >
                        {hasHostBackups() ? 'VMID/Host' : 'VMID'}{' '}
                        {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('type')}
                        style="width: 60px;"
                      >
                        Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('name')}
                        style="width: 150px;"
                      >
                        Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('node')}
                        style="width: 100px;"
                      >
                        Node {sortKey() === 'node' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <Show when={backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'}>
                        <th
                          class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                          onClick={() => handleSort('owner')}
                          style="width: 80px;"
                        >
                          Owner {sortKey() === 'owner' && (sortDirection() === 'asc' ? '▲' : '▼')}
                        </th>
                      </Show>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('backupTime')}
                        style="width: 140px;"
                      >
                        Time {sortKey() === 'backupTime' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <Show when={backupTypeFilter() !== 'snapshot'}>
                        <th
                          class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                          onClick={() => handleSort('size')}
                          style="width: 80px;"
                        >
                          Size {sortKey() === 'size' && (sortDirection() === 'asc' ? '▲' : '▼')}
                        </th>
                      </Show>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                        onClick={() => handleSort('backupType')}
                        style="width: 80px;"
                      >
                        Backup{' '}
                        {sortKey() === 'backupType' && (sortDirection() === 'asc' ? '▲' : '▼')}
                      </th>
                      <Show when={backupTypeFilter() !== 'snapshot'}>
                        <th
                          class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                          onClick={() => handleSort('storage')}
                          style="width: 150px;"
                        >
                          Location{' '}
                          {sortKey() === 'storage' && (sortDirection() === 'asc' ? '▲' : '▼')}
                        </th>
                      </Show>
                      <Show when={backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'}>
                        <th
                          class="px-2 py-1.5 text-center text-[11px] sm:text-xs font-medium uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600"
                          onClick={() => handleSort('verified')}
                          style="width: 60px;"
                        >
                          Verified{' '}
                          {sortKey() === 'verified' && (sortDirection() === 'asc' ? '▲' : '▼')}
                        </th>
                      </Show>
                      <th
                        class="px-2 py-1.5 text-left text-[11px] sm:text-xs font-medium uppercase tracking-wider"
                        style="width: 200px;"
                      >
                        Details
                      </th>
                    </tr>
                  </thead>
                  <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
                    <For each={groupedData()}>
                      {(group) => (
                        <>
                          <tr class="bg-gray-50 dark:bg-gray-900/40">
                            <td
                              colspan={(() => {
                                let cols = 7; // Base columns: VMID, Type, Name, Node, Time, Backup, Details
                                if (backupTypeFilter() === 'all' || backupTypeFilter() === 'remote')
                                  cols++; // Add Owner column
                                if (backupTypeFilter() !== 'snapshot') cols++; // Add Size column
                                if (backupTypeFilter() === 'all' || backupTypeFilter() === 'remote')
                                  cols++; // Add Verified column
                                if (backupTypeFilter() !== 'snapshot') cols++; // Add Location column
                                return cols;
                              })()}
                              class="py-1 pr-2 pl-3 text-[12px] sm:text-sm font-semibold text-slate-700 dark:text-slate-100"
                            >
                              <div class="flex items-center justify-between gap-3">
                                <span class="truncate" title={group.label}>
                                  {group.label}
                                </span>
                                <span class="text-[10px] font-medium text-slate-500 dark:text-slate-400">
                                  {group.items.length}
                                </span>
                              </div>
                            </td>
                          </tr>
                          <For each={group.items}>
                            {(item) => (
                              <tr class="border-t border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/30">
                                <td class="p-0.5 pl-5 pr-1.5 text-sm align-middle">{item.vmid}</td>
                                <td class="p-0.5 px-1.5 align-middle">
                                  <span
                                    class={`inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium ${
                                      item.type === 'VM'
                                        ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
                                        : item.type === 'Host'
                                          ? 'bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300'
                                          : 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300'
                                    }`}
                                  >
                                    {item.type}
                                  </span>
                                </td>
                                <td class="p-0.5 px-1.5 text-sm align-middle">
                                  {item.name || '-'}
                                </td>
                                <td class="p-0.5 px-1.5 text-sm align-middle">{item.node}</td>
                                <Show
                                  when={
                                    backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'
                                  }
                                >
                                  <td class="p-0.5 px-1.5 text-xs align-middle text-gray-500 dark:text-gray-400">
                                    {item.owner ? item.owner.split('@')[0] : '-'}
                                  </td>
                                </Show>
                                <td
                                  class={`p-0.5 px-1.5 text-xs align-middle ${getAgeColorClass(item.backupTime)}`}
                                >
                                  {formatTime(item.backupTime * 1000)}
                                </td>
                                <Show when={backupTypeFilter() !== 'snapshot'}>
                                  <td
                                    class={`p-0.5 px-1.5 align-middle ${getSizeColor(item.size)}`}
                                  >
                                    {item.size ? formatBytes(item.size) : '-'}
                                  </td>
                                </Show>
                                <td class="p-0.5 px-1.5 align-middle">
                                  <div class="flex items-center gap-1">
                                    <span
                                      class={`inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium ${
                                        item.backupType === 'snapshot'
                                          ? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/20 dark:text-yellow-300'
                                          : item.backupType === 'local'
                                            ? 'bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300'
                                            : 'bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300'
                                      }`}
                                    >
                                      {item.backupType === 'snapshot'
                                        ? 'Snapshot'
                                        : item.backupType === 'local'
                                          ? 'PVE'
                                          : 'PBS'}
                                    </span>
                                    <Show when={item.encrypted}>
                                      <span
                                        title="Encrypted backup"
                                        class="text-green-600 dark:text-green-400 inline-block ml-1"
                                      >
                                        <svg
                                          xmlns="http://www.w3.org/2000/svg"
                                          class="h-4 w-4"
                                          viewBox="0 0 20 20"
                                          fill="currentColor"
                                        >
                                          <path
                                            fill-rule="evenodd"
                                            d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
                                            clip-rule="evenodd"
                                          />
                                        </svg>
                                      </span>
                                    </Show>
                                    <Show when={item.protected}>
                                      <span
                                        title="Protected backup"
                                        class="text-blue-600 dark:text-blue-400 inline-block ml-1"
                                      >
                                        <svg
                                          xmlns="http://www.w3.org/2000/svg"
                                          class="h-4 w-4"
                                          viewBox="0 0 20 20"
                                          fill="currentColor"
                                        >
                                          <path
                                            fill-rule="evenodd"
                                            d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
                                            clip-rule="evenodd"
                                          />
                                        </svg>
                                      </span>
                                    </Show>
                                  </div>
                                </td>
                                <Show when={backupTypeFilter() !== 'snapshot'}>
                                  <td class="p-0.5 px-1.5 text-sm align-middle">
                                    {item.storage ||
                                      (item.datastore &&
                                        (item.namespace && item.namespace !== 'root'
                                          ? `${item.datastore}/${item.namespace}`
                                          : item.datastore)) ||
                                      '-'}
                                  </td>
                                </Show>
                                <Show
                                  when={
                                    backupTypeFilter() === 'all' || backupTypeFilter() === 'remote'
                                  }
                                >
                                  <td class="p-0.5 px-1.5 text-center align-middle">
                                    {item.backupType === 'remote' ? (
                                      item.verified ? (
                                        <span title="PBS backup verified">
                                          <svg
                                            class="w-4 h-4 text-green-500 dark:text-green-400 inline"
                                            fill="none"
                                            viewBox="0 0 24 24"
                                            stroke="currentColor"
                                          >
                                            <path
                                              stroke-linecap="round"
                                              stroke-linejoin="round"
                                              stroke-width="2"
                                              d="M5 13l4 4L19 7"
                                            />
                                          </svg>
                                        </span>
                                      ) : (
                                        <span title="PBS backup not yet verified">
                                          <svg
                                            class="w-4 h-4 text-gray-400 dark:text-gray-500 inline"
                                            fill="none"
                                            viewBox="0 0 24 24"
                                            stroke="currentColor"
                                          >
                                            <path
                                              stroke-linecap="round"
                                              stroke-linejoin="round"
                                              stroke-width="2"
                                              d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                                            />
                                          </svg>
                                        </span>
                                      )
                                    ) : (
                                      <span
                                        class="text-gray-400 dark:text-gray-500"
                                        title="Verification only available for PBS backups"
                                      >
                                        -
                                      </span>
                                    )}
                                  </td>
                                </Show>
                                <td
                                  class="p-0.5 px-1.5 cursor-help align-middle"
                                  onMouseEnter={(e) => {
                                    const details = [];

                                    if (item.backupType === 'snapshot') {
                                      details.push(item.backupName);
                                      if (item.description) {
                                        details.push(item.description);
                                      }
                                    } else if (item.backupType === 'local') {
                                      details.push(item.backupName);
                                    } else if (item.backupType === 'remote') {
                                      if (item.protected) details.push('Protected');
                                      // For PBS backups, show the notes field which contains the backup description
                                      const pbsDescription =
                                        item.description ||
                                        (item.name && item.name !== '-' ? item.name : '');
                                      if (pbsDescription && pbsDescription.trim()) {
                                        details.push(pbsDescription);
                                      }
                                    }

                                    const fullText = details.join(' • ') || '-';
                                    if (fullText.length > 35) {
                                      const rect = e.currentTarget.getBoundingClientRect();
                                      showTooltip(fullText, rect.left, rect.top, {
                                        align: 'left',
                                        direction: 'up',
                                        maxWidth: 260,
                                      });
                                    }
                                  }}
                                  onMouseLeave={() => {
                                    hideTooltip();
                                  }}
                                >
                                  {(() => {
                                    const details = [];

                                    if (item.backupType === 'snapshot') {
                                      details.push(item.backupName);
                                      if (item.description) {
                                        details.push(item.description);
                                      }
                                    } else if (item.backupType === 'local') {
                                      details.push(truncateMiddle(item.backupName, 30));
                                    } else if (item.backupType === 'remote') {
                                      if (item.protected) details.push('Protected');
                                      // For PBS backups, show the notes field which contains the backup description
                                      const pbsDescription =
                                        item.description ||
                                        (item.name && item.name !== '-' ? item.name : '');
                                      if (pbsDescription && pbsDescription.trim()) {
                                        details.push(pbsDescription);
                                      }
                                    }

                                    const fullText = details.join(' • ') || '-';
                                    const displayText =
                                      fullText.length > 35
                                        ? fullText.substring(0, 32) + '...'
                                        : fullText;

                                    return displayText;
                                  })()}
                                </td>
                              </tr>
                            )}
                          </For>
                        </>
                      )}
                    </For>
                  </tbody>
                </table>
              </Show>
            </Show>
          </div>
        </Card>
      </Show>
    </div>
  );
};

export default UnifiedBackups;
