import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';

import ConfirmDeleteModal from '@/entrypoints/popup/components/Dialogs/ConfirmDeleteModal';
import DeleteFolderModal from '@/entrypoints/popup/components/Folders/DeleteFolderModal';
import FolderBreadcrumb from '@/entrypoints/popup/components/Folders/FolderBreadcrumb';
import FolderModal from '@/entrypoints/popup/components/Folders/FolderModal';
import HeaderButton from '@/entrypoints/popup/components/HeaderButton';
import { HeaderIconType } from '@/entrypoints/popup/components/Icons/HeaderIcons';
import CurrentSiteSuggestion from '@/entrypoints/popup/components/Items/CurrentSiteSuggestion';
import FolderPill from '@/entrypoints/popup/components/Items/FolderPill';
import ItemCard from '@/entrypoints/popup/components/Items/ItemCard';
import ItemFilterDropdown from '@/entrypoints/popup/components/Items/ItemFilterDropdown';
import { ITEM_TYPE_OPTIONS } from '@/entrypoints/popup/components/Items/ItemTypeSelector';
import LoadingSpinner from '@/entrypoints/popup/components/LoadingSpinner';
import ReloadButton from '@/entrypoints/popup/components/ReloadButton';
import { useApp } from '@/entrypoints/popup/context/AppContext';
import { useDb } from '@/entrypoints/popup/context/DbContext';
import { useHeaderButtons } from '@/entrypoints/popup/context/HeaderButtonsContext';
import { useLoading } from '@/entrypoints/popup/context/LoadingContext';
import { useListKeyboardNav } from '@/entrypoints/popup/hooks/useListKeyboardNav';
import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate';
import { useVaultSync } from '@/entrypoints/popup/hooks/useVaultSync';
import { PopoutUtility } from '@/entrypoints/popup/utils/PopoutUtility';

import type { Folder } from '@/utils/db/repositories/FolderRepository';
import type { CredentialSortOrder } from '@/utils/db/repositories/SettingsRepository';
import type { Item } from '@/utils/dist/core/models/vault';
import { canHaveSubfolders, getDescendantFolderIds, getFolderPath, getRecursiveItemCount } from '@/utils/FolderUtils';
import { applyTypeFilter, isItemTypeFilter, parseItemFilterType, type ItemFilterType } from '@/utils/ItemFilters';
import { LocalPreferencesService } from '@/utils/LocalPreferencesService';

import { useMinDurationLoading } from '@/hooks/useMinDurationLoading';

const FILTER_STORAGE_KEY = 'items-filter';
const FILTER_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes

/*
 * Minimum number of vault entries before the current-site suggestion is shown.
 * With only a handful of entries (e.g. right after onboarding) the whole list is
 * already visible, so a suggestion just duplicates an entry that's right there.
 */
const CURRENT_SITE_SUGGESTION_MIN_ITEMS = 5;

/**
 * Sort order options with their translation keys
 */
const SORT_OPTIONS: { value: CredentialSortOrder; labelKey: string }[] = [
  { value: 'OldestFirst', labelKey: 'items.sort.oldestFirst' },
  { value: 'NewestFirst', labelKey: 'items.sort.newestFirst' },
  { value: 'Alphabetical', labelKey: 'items.sort.alphabetical' },
];

/**
 * Get stored filter from localStorage if not expired
 */
const getStoredFilter = (): ItemFilterType => {
  try {
    const stored = localStorage.getItem(FILTER_STORAGE_KEY);
    if (!stored) {
      return 'all';
    }

    const { filter, timestamp } = JSON.parse(stored);
    const now = Date.now();

    // Check if expired (5 minutes)
    if (now - timestamp > FILTER_EXPIRY_MS) {
      localStorage.removeItem(FILTER_STORAGE_KEY);
      return 'all';
    }

    return filter as ItemFilterType;
  } catch {
    return 'all';
  }
};

/**
 * Store filter in localStorage with timestamp
 */
const storeFilter = (filter: ItemFilterType): void => {
  try {
    localStorage.setItem(FILTER_STORAGE_KEY, JSON.stringify({
      filter,
      timestamp: Date.now()
    }));
  } catch {
    // Ignore storage errors
  }
};

/**
 * Represents a folder with item count
 */
type FolderWithCount = {
  id: string;
  name: string;
  itemCount: number;
};

/**
 * Items list page with folder support.
 */
const ItemsList: React.FC = () => {
  const { t } = useTranslation();
  const { folderId: folderIdParam } = useParams<{ folderId?: string }>();
  const location = useLocation();
  const [searchParams] = useSearchParams();
  const dbContext = useDb();
  const app = useApp();
  const navigate = useNavigate();
  const { syncVault } = useVaultSync();
  const { executeVaultMutationAsync } = useVaultMutate();
  const { setHeaderButtons, setBackButtonTitle } = useHeaderButtons();
  const [items, setItems] = useState<Item[]>([]);
  // Initialize searchTerm from URL query parameter (restored from back navigation or popup restore)
  const [searchTerm, setSearchTerm] = useState(() => {
    return searchParams.get('search') || '';
  });
  // URL filter takes precedence so navigating between folders preserves the active filter.
  const [filterType, setItemFilterType] = useState<ItemFilterType>(() => {
    const urlFilter = searchParams.get('filter');
    return urlFilter ? parseItemFilterType(urlFilter) : getStoredFilter();
  });
  const [showFolderModal, setShowFolderModal] = useState(false);
  const [showDeleteFolderModal, setShowDeleteFolderModal] = useState(false);
  const [showEditFolderModal, setShowEditFolderModal] = useState(false);
  const [deleteItemId, setDeleteItemId] = useState<string | null>(null);
  const [highlightedItemId, setHighlightedItemId] = useState<string | null>(null);
  const [recentlyDeletedCount, setRecentlyDeletedCount] = useState(0);
  const [folderRefreshKey, setFolderRefreshKey] = useState(0);
  const [sortOrder, setSortOrder] = useState<CredentialSortOrder>('NewestFirst');
  const [showSortMenu, setShowSortMenu] = useState(false);
  const [showFolders, setShowFolders] = useState(true);
  const { setIsInitialLoading } = useLoading();
  const searchInputRef = useRef<HTMLInputElement | null>(null);

  // Load showFolders preference from storage on mount
  useEffect(() => {
    LocalPreferencesService.getShowFolders().then(setShowFolders);
  }, []);

  // Derive current folder from URL params
  const currentFolderId = folderIdParam ?? null;

  // Get current folder name from database
  const currentFolderName = useMemo(() => {
    // folderRefreshKey is included in deps to force re-computation when folder is renamed
    void folderRefreshKey;
    if (!currentFolderId || !dbContext?.sqliteClient) {
      return null;
    }
    const folders = dbContext.sqliteClient.folders.getAll();
    const folder = folders.find((f: { Id: string; Name: string }) => f.Id === currentFolderId);
    return folder?.Name ?? null;
  }, [currentFolderId, dbContext?.sqliteClient, folderRefreshKey]);

  // Get current folder's full path (for relative path computation in search results)
  const currentFolderPath = useMemo(() => {
    if (!currentFolderId || !dbContext?.sqliteClient) {
      return null;
    }
    const folders = dbContext.sqliteClient.folders.getAll();
    const path = getFolderPath(currentFolderId, folders);
    return path.length > 0 ? path : null;
  }, [currentFolderId, dbContext?.sqliteClient]);

  /**
   * Loading state with minimum duration for more fluid UX.
   */
  const [isLoading, setIsLoading] = useMinDurationLoading(true, 100);

  /**
   * Update URL when search term changes to persist it in navigation history.
   * Preserves any existing filter param in the URL (set by folder navigation) — the
   * filter is initialized from the URL on mount and otherwise managed in state, so
   * we just leave whatever filter param is there alone.
   */
  useEffect(() => {
    const params = new URLSearchParams(location.search);
    if (searchTerm) {
      params.set('search', searchTerm);
    } else {
      params.delete('search');
    }
    const queryString = params.toString();
    const newUrl = queryString ? `${location.pathname}?${queryString}` : location.pathname;

    // Only update if the URL actually changed
    const currentUrl = location.pathname + location.search;
    if (newUrl !== currentUrl) {
      navigate(newUrl, { replace: true });
    }
  }, [searchTerm, location.pathname, location.search, navigate]);

  /**
   * Reset search and filter when navigating via the vault tab (with resetFilters state).
   */
  useEffect(() => {
    const state = location.state as { resetFilters?: boolean } | null;
    if (state?.resetFilters) {
      setSearchTerm('');
      setItemFilterType('all');
      localStorage.removeItem(FILTER_STORAGE_KEY);
      // Clear the state to prevent re-triggering on subsequent renders
      navigate(location.pathname, { replace: true, state: {} });
    }
  }, [location.state, location.pathname, navigate]);

  /**
   * Handle add new item.
   * Navigate to add item page, pre-selecting the item type if filtering by type.
   * Also pre-selects the current folder if we're inside a folder.
   */
  const handleAddItem = useCallback(() : void => {
    const params = new URLSearchParams();

    // If filtering by an item type, pre-select that type for the new item
    if (isItemTypeFilter(filterType)) {
      params.set('type', filterType);
    }

    // Pre-select the current folder if we're inside a folder
    if (currentFolderId) {
      params.set('folderId', currentFolderId);
    }

    const queryString = params.toString();
    navigate(queryString ? `/items/add?${queryString}` : '/items/add');
  }, [navigate, filterType, currentFolderId]);

  /**
   * Handle add new folder.
   */
  const handleAddFolder = useCallback(() : void => {
    setShowFolderModal(true);
  }, []);

  /**
   * Handle save folder.
   * Creates folder in current location (root or inside current folder).
   */
  const handleSaveFolder = useCallback(async (folderName: string) : Promise<void> => {
    if (!dbContext?.sqliteClient) {
      console.error('[FOLDER DEBUG] No sqliteClient available');
      return;
    }

    await executeVaultMutationAsync(async () => {
      // Create folder in current location (currentFolderId = null means root)
      await dbContext.sqliteClient!.folders.create(folderName, currentFolderId);
    });

    // Refresh items to show the new folder
    const results = dbContext.sqliteClient!.items.getAll();
    setItems(results);
  }, [dbContext, currentFolderId, executeVaultMutationAsync]);

  /**
   * Duplicate an item via the item context menu.
   */
  const handleDuplicateItem = useCallback(async (itemId: string) : Promise<void> => {
    if (!dbContext?.sqliteClient) {
      return;
    }

    let newItemId: string | null = null;
    await executeVaultMutationAsync(async () => {
      newItemId = await dbContext.sqliteClient!.items.duplicate(itemId);
    });

    // Refresh items to show the new duplicate
    const results = dbContext.sqliteClient!.items.getAll();
    setItems(results);

    // Scroll to and briefly highlight the new duplicate so it's clear where it landed
    setHighlightedItemId(newItemId);
  }, [dbContext, executeVaultMutationAsync]);

  /**
   * Scroll the highlighted item (a freshly created duplicate) into view once it's
   * rendered, then clear the highlight after a short moment.
   */
  useEffect(() => {
    if (!highlightedItemId) {
      return;
    }

    // Wait a frame so the re-rendered list contains the new item before scrolling.
    requestAnimationFrame(() => {
      document.querySelector(`[data-item-id="${highlightedItemId}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
    });

    const timer = setTimeout(() => setHighlightedItemId(null), 2000);
    return (): void => clearTimeout(timer);
  }, [highlightedItemId]);

  /**
   * Move an item to the trash after confirmation via the item context menu.
   */
  const handleConfirmDeleteItem = useCallback(async () : Promise<void> => {
    if (!dbContext?.sqliteClient || !deleteItemId) {
      return;
    }

    const itemId = deleteItemId;
    setDeleteItemId(null);

    await executeVaultMutationAsync(async () => {
      await dbContext.sqliteClient!.items.trash(itemId);
    });

    // Refresh items to reflect the deletion
    const results = dbContext.sqliteClient!.items.getAll();
    setItems(results);
    const deletedCount = dbContext.sqliteClient!.items.getRecentlyDeletedCount();
    setRecentlyDeletedCount(deletedCount);
  }, [dbContext, deleteItemId, executeVaultMutationAsync]);

  /**
   * Handle delete folder (keep items, move them to root).
   */
  const handleDeleteFolderOnly = useCallback(async () : Promise<void> => {
    if (!dbContext?.sqliteClient || !currentFolderId) {
      return;
    }

    // Get the current folder to determine parent before deletion
    const allFolders = dbContext.sqliteClient.folders.getAll();
    const currentFolder = allFolders.find(f => f.Id === currentFolderId);
    const parentFolderId = currentFolder?.ParentFolderId ?? null;

    await executeVaultMutationAsync(async () => {
      await dbContext.sqliteClient!.folders.delete(currentFolderId);
    });

    // Refresh items list to reflect changes
    const results = dbContext.sqliteClient!.items.getAll();
    setItems(results);
    const deletedCount = dbContext.sqliteClient!.items.getRecentlyDeletedCount();
    setRecentlyDeletedCount(deletedCount);

    // Navigate to parent folder if it exists, otherwise root
    if (parentFolderId) {
      navigate(`/items/folder/${parentFolderId}`);
    } else {
      navigate('/items');
    }
  }, [dbContext, currentFolderId, executeVaultMutationAsync, navigate]);

  /**
   * Handle delete folder and all its contents.
   */
  const handleDeleteFolderAndContents = useCallback(async () : Promise<void> => {
    if (!dbContext?.sqliteClient || !currentFolderId) {
      return;
    }

    // Get the current folder to determine parent before deletion
    const allFolders = dbContext.sqliteClient.folders.getAll();
    const currentFolder = allFolders.find(f => f.Id === currentFolderId);
    const parentFolderId = currentFolder?.ParentFolderId ?? null;

    await executeVaultMutationAsync(async () => {
      await dbContext.sqliteClient!.folders.deleteWithContents(currentFolderId);
    });

    // Refresh items list to reflect changes
    const results = dbContext.sqliteClient!.items.getAll();
    setItems(results);
    const deletedCount = dbContext.sqliteClient!.items.getRecentlyDeletedCount();
    setRecentlyDeletedCount(deletedCount);

    // Navigate to parent folder if it exists, otherwise root
    if (parentFolderId) {
      navigate(`/items/folder/${parentFolderId}`);
    } else {
      navigate('/items');
    }
  }, [dbContext, currentFolderId, executeVaultMutationAsync, navigate]);

  /**
   * Handle edit/rename folder.
   */
  const handleEditFolder = useCallback(async (newName: string) : Promise<void> => {
    if (!dbContext?.sqliteClient || !currentFolderId) {
      return;
    }

    await executeVaultMutationAsync(async () => {
      await dbContext.sqliteClient!.folders.update(currentFolderId, newName);
    });

    // Trigger re-computation of currentFolderName
    setFolderRefreshKey(prev => prev + 1);

    // Close modal
    setShowEditFolderModal(false);
  }, [dbContext, currentFolderId, executeVaultMutationAsync]);

  /**
   * Retrieve latest vault and refresh the items list.
   */
  const onRefresh = useCallback(async () : Promise<void> => {
    if (!dbContext?.sqliteClient) {
      return;
    }

    try {
      // Sync vault and load items
      await syncVault({
        /**
         * On success.
         */
        onSuccess: async (_hasNewVault) => {
          // Items list is refreshed automatically when the (new) sqlite client is available via useEffect hook below.
        },
        /**
         * On offline.
         */
        onOffline: () => {
          // Continue with local vault in offline mode.
        },
        /**
         * On error.
         */
        onError: async (error) => {
          console.error('Error syncing vault:', error);
        },
      });
    } catch (err) {
      console.error('Error refreshing items:', err);
      await app.logout('Error while syncing vault, please re-authenticate.');
    }
  }, [dbContext, app, syncVault]);

  /**
   * Get latest vault from server and refresh the items list.
   */
  const syncVaultAndRefresh = useCallback(async () : Promise<void> => {
    setIsLoading(true);
    await onRefresh();
    setIsLoading(false);
  }, [onRefresh, setIsLoading]);

  // Set header buttons on mount and clear on unmount
  useEffect((): (() => void) => {
    const headerButtonsJSX = (
      <div className="flex items-center gap-2">
        {!PopoutUtility.isPopup() && (
          <HeaderButton
            onClick={() => PopoutUtility.openInNewPopup()}
            title={t('common.openInNewWindow')}
            iconType={HeaderIconType.EXPAND}
          />
        )}
        <HeaderButton
          onClick={handleAddItem}
          title={t('items.addNewItem')}
          iconType={HeaderIconType.PLUS}
        />
      </div>
    );

    setHeaderButtons(headerButtonsJSX);
    return () => setHeaderButtons(null);
  }, [setHeaderButtons, handleAddItem, t]);

  // Set back button title for folder view
  useEffect(() => {
    if (folderIdParam && dbContext?.sqliteClient) {
      const allFolders = dbContext.sqliteClient.folders.getAll();
      const currentFolder = allFolders.find(f => f.Id === folderIdParam);

      if (currentFolder && currentFolder.ParentFolderId) {
        // Has parent folder - show parent folder name
        const parentFolder = allFolders.find(f => f.Id === currentFolder.ParentFolderId);
        if (parentFolder) {
          setBackButtonTitle(parentFolder.Name);
        } else {
          setBackButtonTitle(t('items.title'));
        }
      } else if (currentFolder) {
        // Root folder - show "Items"
        setBackButtonTitle(t('items.title'));
      } else {
        // Folder not found - fallback to "Items"
        setBackButtonTitle(t('items.title'));
      }
    }

    return (): void => setBackButtonTitle(null);
  }, [folderIdParam, dbContext?.sqliteClient, setBackButtonTitle, t]);

  /**
   * Load items list on mount and on sqlite client change.
   */
  useEffect(() => {
    /**
     * Refresh items list when a (new) sqlite client is available.
     */
    const refreshItems = async () : Promise<void> => {
      if (dbContext?.sqliteClient) {
        setIsLoading(true);
        const results = dbContext.sqliteClient?.items.getAll() ?? [];
        setItems(results);
        // Also get recently deleted count
        const deletedCount = dbContext.sqliteClient?.items.getRecentlyDeletedCount() ?? 0;
        setRecentlyDeletedCount(deletedCount);
        // Load sort order from settings
        const savedSortOrder = dbContext.sqliteClient?.settings.getCredentialsSortOrder() ?? 'NewestFirst';
        setSortOrder(savedSortOrder);
        setIsLoading(false);
        setIsInitialLoading(false);
      }
    };

    refreshItems();
  }, [dbContext?.sqliteClient, setIsLoading, setIsInitialLoading]);

  /**
   * Get the title based on the active filter and current folder.
   * An active filter takes precedence over the folder name so the title matches the
   * filter that's actually being applied — same behavior as the root view.
   */
  const getFilterTitle = () : string => {
    switch (filterType) {
      case 'passkeys':
        return t('common.passkeys');
      case 'attachments':
        return t('common.attachments');
      case 'totp':
        return t('items.filters.totp');
      case 'all':
        if (currentFolderId && currentFolderName) {
          return currentFolderName;
        }
        return t('items.title');
      default:
        // Check if it's an item type filter
        if (isItemTypeFilter(filterType)) {
          const itemTypeOption = ITEM_TYPE_OPTIONS.find(opt => opt.type === filterType);
          if (itemTypeOption) {
            return t(itemTypeOption.titleKey);
          }
        }
        if (currentFolderId && currentFolderName) {
          return currentFolderName;
        }
        return t('items.title');
    }
  };

  /**
   * Navigate into a folder via URL, preserving the active filter so the folder view
   * shows the same subset reflected in the folder pill's badge count.
   */
  const handleFolderClick = useCallback((folderId: string, _folderName: string) => {
    setSearchTerm(''); // Clear search when entering folder
    navigate(`/items/folder/${folderId}?filter=${encodeURIComponent(filterType)}`);
  }, [navigate, filterType]);

  /**
   * Get folders with item counts.
   * - At root level: show only root folders (ParentFolderId IS NULL)
   * - Inside a folder: show only direct subfolders (ParentFolderId = currentFolderId)
   * - Counts include items in the folder AND all subfolders recursively
   * - Counts respect the active type/feature filter so the badge matches what the user sees inside.
   */
  const getFoldersWithCounts = (): FolderWithCount[] => {
    if (searchTerm) {
      return []; // Don't show folders when searching
    }

    if (!dbContext?.sqliteClient) {
      return [];
    }

    // Get all folders directly from the database
    const allFolders = dbContext.sqliteClient.folders.getAll();

    // Filter folders based on current location
    const relevantFolders = allFolders.filter((folder: Folder) => {
      if (currentFolderId) {
        // Inside a folder: show only direct children
        return folder.ParentFolderId === currentFolderId;
      } else {
        /*
         * At root: show only root-level folders
         * Check for both null and undefined since SQLite might return either
         */
        return folder.ParentFolderId === null || folder.ParentFolderId === undefined;
      }
    });

    // Count items per folder (direct items only) using items that pass the active filter
    const filteredForCounts = applyTypeFilter(items, filterType);
    const directFolderCounts = new Map<string, number>();
    filteredForCounts.forEach((item: Item) => {
      if (item.FolderId) {
        directFolderCounts.set(item.FolderId, (directFolderCounts.get(item.FolderId) || 0) + 1);
      }
    });

    /**
     * Recursively count items in a folder and all its subfolders.
     * @param folderId - The folder ID to count items for
     * @returns Total count of items in this folder and all descendant folders
     */
    const getRecursiveItemCountLocal = (folderId: string): number => {
      // Start with direct items in this folder
      let count = directFolderCounts.get(folderId) || 0;

      // Add counts from all child folders recursively
      const childFolderIds = getDescendantFolderIds(folderId, allFolders);
      for (const childId of childFolderIds) {
        count += directFolderCounts.get(childId) || 0;
      }

      return count;
    };

    // Build result with recursive counts
    const result = relevantFolders.map((folder: Folder) => ({
      id: folder.Id,
      name: folder.Name,
      itemCount: getRecursiveItemCountLocal(folder.Id)
    })).sort((a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name));

    return result;
  };

  /**
   * Filter items based on current view (folder, search, filter type)
   */
  const filteredItems = ((): Item[] => {
    // Filter by current folder (if in folder view)
    let folderScoped: Item[];
    if (currentFolderId !== null) {
      if (searchTerm) {
        // When searching inside a folder, include items in subfolders too
        const allFolders = dbContext?.sqliteClient?.folders.getAll() || [];
        const childFolderIds = getDescendantFolderIds(currentFolderId, allFolders);
        const allFolderIds = [currentFolderId, ...childFolderIds];
        folderScoped = items.filter((item: Item) =>
          item.FolderId !== null && item.FolderId !== undefined && allFolderIds.includes(item.FolderId)
        );
      } else {
        // When not searching, only show direct items (not items in subfolders)
        folderScoped = items.filter((item: Item) => item.FolderId === currentFolderId);
      }
    } else if (!searchTerm && showFolders) {
      /*
       * When showing folders (checkbox ON): only show root items (exclude items in folders)
       * When not showing folders (checkbox OFF): show all items flat
       */
      folderScoped = items.filter((item: Item) => !item.FolderId);
    } else {
      folderScoped = items;
    }

    const typeFiltered = applyTypeFilter(folderScoped, filterType);

    const searchLower = searchTerm.toLowerCase().trim();
    if (!searchLower) {
      return typeFiltered;
    }

    const searchWords = searchLower.split(/\s+/).filter(word => word.length > 0);

    return typeFiltered.filter((item: Item) => {
      const searchableFields: string[] = [
        item.Name?.toLowerCase() || '',
      ];

      item.Fields?.forEach(field => {
        const value = Array.isArray(field.Value) ? field.Value.join(' ').toLowerCase() : (field.Value || '').toLowerCase();
        searchableFields.push(value);
        searchableFields.push(field.Label.toLowerCase());
      });

      return searchWords.every(word =>
        searchableFields.some(field => field.includes(word))
      );
    });
  })();

  /**
   * Sort the filtered items based on the current sort order.
   */
  const sortedItems = useMemo(() => {
    const itemsCopy = [...filteredItems];
    switch (sortOrder) {
      case 'NewestFirst':
        return itemsCopy.sort((a, b) =>
          new Date(b.CreatedAt || 0).getTime() - new Date(a.CreatedAt || 0).getTime()
        );
      case 'Alphabetical':
        return itemsCopy.sort((a, b) =>
          (a.Name || '').localeCompare(b.Name || '')
        );
      case 'OldestFirst':
      default:
        return itemsCopy.sort((a, b) =>
          new Date(a.CreatedAt || 0).getTime() - new Date(b.CreatedAt || 0).getTime()
        );
    }
  }, [filteredItems, sortOrder]);

  const folders = getFoldersWithCounts();

  /**
   * Calculate total item count including items in current folder and all child folders.
   * Used for the delete folder modal to show accurate count.
   */
  const totalItemCountInFolderTree = useMemo(() => {
    if (!currentFolderId || !dbContext?.sqliteClient) {
      return filteredItems.length;
    }

    const allFolders = dbContext.sqliteClient.folders.getAll();
    return getRecursiveItemCount(currentFolderId, items, allFolders);
  }, [currentFolderId, items, dbContext?.sqliteClient, filteredItems.length]);

  /**
   * Check if the current folder can have subfolders (not at max depth).
   * At root level (currentFolderId = null), we can always create folders.
   */
  const canCreateSubfolder = useMemo(() => {
    if (!currentFolderId) {
      return true; // Root level, always allowed
    }
    if (!dbContext?.sqliteClient) {
      return false;
    }
    const allFolders = dbContext.sqliteClient.folders.getAll();
    return canHaveSubfolders(currentFolderId, allFolders);
  }, [currentFolderId, dbContext?.sqliteClient]);

  /**
   * Check if all items are in folders (no items at root level but items exist in folders).
   * This is used to show a helpful message when the user has imported credentials that were all in folders.
   */
  const hasItemsInFoldersOnly = items.length > 0 && items.every((item: Item) => item.FolderId !== null);

  /**
   * Open the Nth item in the current sorted list (ArrowRight on an item).
   */
  const handleActivateItem = useCallback((index: number): void => {
    const item = sortedItems[index];
    if (!item) {
      return;
    }
    const url = searchTerm ? `/items/${item.Id}?returnSearch=${encodeURIComponent(searchTerm)}` : `/items/${item.Id}`;
    navigate(url);
  }, [sortedItems, searchTerm, navigate]);

  /**
   * Enter the Nth folder (ArrowRight on a folder pill).
   */
  const handleActivateFolder = useCallback((index: number): void => {
    const folder = folders[index];
    if (!folder) {
      return;
    }
    handleFolderClick(folder.id, folder.name);
  }, [folders, handleFolderClick]);

  /**
   * Clear the search input (ArrowUp inside the search field with text).
   */
  const handleClearSearch = useCallback((): void => {
    setSearchTerm('');
  }, []);

  /**
   * Go up one folder (ArrowLeft).
   */
  const handleGoBack = useCallback((): void => {
    if (!currentFolderId || !dbContext?.sqliteClient) {
      return;
    }
    const allFolders = dbContext.sqliteClient.folders.getAll();
    const current = allFolders.find((f: Folder) => f.Id === currentFolderId);
    const parentFolderId = current?.ParentFolderId ?? null;
    if (parentFolderId) {
      navigate(`/items/folder/${parentFolderId}`);
    } else {
      navigate('/items');
    }
  }, [currentFolderId, dbContext, navigate]);

  const { activeKind, activeIndex, itemIdFor, folderIdFor, activeDescendantId } = useListKeyboardNav({
    folderCount: folders.length,
    itemCount: sortedItems.length,
    searchInputRef,
    resetKey: currentFolderId,
    onActivateFolder: handleActivateFolder,
    onActivateItem: handleActivateItem,
    onGoBack: handleGoBack,
    onClearSearch: handleClearSearch,
  });

  if (isLoading) {
    return (
      <div className="flex justify-center items-center p-8">
        <LoadingSpinner />
      </div>
    );
  }

  return (
    <div>
      {/* Breadcrumb navigation - only show when inside a folder */}
      <FolderBreadcrumb folderId={currentFolderId} />

      <div className="flex justify-between items-center gap-2 mb-4">
        <div className="min-w-0 flex-1 flex items-center gap-2">
          <ItemFilterDropdown
            title={getFilterTitle()}
            count={filteredItems.length}
            activeFilter={filterType}
            recentlyDeletedCount={recentlyDeletedCount}
            showFoldersToggle={!currentFolderId}
            showFolders={showFolders}
            onToggleShowFolders={(next) => {
              setShowFolders(next);
              LocalPreferencesService.setShowFolders(next);
            }}
            onSelectFilter={(newFilter) => {
              setItemFilterType(newFilter);
              storeFilter(newFilter);
            }}
            onSelectRecentlyDeleted={() => navigate('/items/deleted')}
          />
          {/* Edit and Delete buttons when inside a folder */}
          {currentFolderId && (
            <div className="flex items-center gap-1 shrink-0">
              <button
                onClick={() => setShowEditFolderModal(true)}
                title={t('items.editFolder')}
                className="p-1.5 text-gray-400 hover:text-orange-500 dark:text-gray-500 dark:hover:text-orange-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
              >
                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                </svg>
              </button>
              <button
                onClick={() => setShowDeleteFolderModal(true)}
                title={t('items.deleteFolder')}
                className="p-1.5 text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
              >
                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <polyline points="3 6 5 6 21 6" />
                  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
                </svg>
              </button>
            </div>
          )}
        </div>
        <div className="flex items-center gap-1">
          {/* Sort button */}
          <div className="relative">
            <button
              onClick={() => setShowSortMenu(!showSortMenu)}
              className="p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
              title={t('items.sort.title')}
            >
              <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <line x1="4" y1="6" x2="16" y2="6" />
                <line x1="4" y1="12" x2="12" y2="12" />
                <line x1="4" y1="18" x2="8" y2="18" />
                <polyline points="15 15 18 18 21 15" />
                <line x1="18" y1="12" x2="18" y2="18" />
              </svg>
            </button>
            {showSortMenu && (
              <>
                <div
                  className="fixed inset-0 z-10"
                  onClick={() => setShowSortMenu(false)}
                />
                <div className="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl ring-1 ring-black/5 dark:ring-white/10 z-20">
                  <div className="py-1">
                    {SORT_OPTIONS.map((option) => (
                      <button
                        key={option.value}
                        onClick={async () => {
                          setSortOrder(option.value);
                          setShowSortMenu(false);
                          // Save to settings and trigger vault sync
                          await executeVaultMutationAsync(async () => {
                            dbContext.sqliteClient?.settings.setCredentialsSortOrder(option.value);
                          });
                        }}
                        className={`w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 ${
                          sortOrder === option.value ? 'text-orange-600 dark:text-orange-400' : 'text-gray-700 dark:text-gray-300'
                        }`}
                      >
                        {sortOrder === option.value ? (
                          <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                            <polyline points="20 6 9 17 4 12" />
                          </svg>
                        ) : (
                          <span className="w-4" />
                        )}
                        <span>{t(option.labelKey)}</span>
                      </button>
                    ))}
                  </div>
                </div>
              </>
            )}
          </div>
          <ReloadButton onClick={syncVaultAndRefresh} />
        </div>
      </div>

      {items.length > 0 ? (
        <div className="mb-4">
          <input
            ref={searchInputRef}
            type="text"
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
            placeholder={`${t('content.searchVault')}`}
            autoFocus
            role="combobox"
            aria-controls="items-list"
            aria-expanded={sortedItems.length > 0 || folders.length > 0}
            aria-activedescendant={activeDescendantId}
            aria-autocomplete="list"
            className="w-full p-2 border dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-primary-500 focus:border-primary-500"
          />
        </div>
      ) : (
        <></>
      )}

      {/* Current-site suggestion: show a suggestion for the current site's matching item(s) to quickly view/open them. */}
      {items.length > CURRENT_SITE_SUGGESTION_MIN_ITEMS && !currentFolderId && !searchTerm && filterType === 'all' && (
        <CurrentSiteSuggestion onSearch={setSearchTerm} />
      )}

      {items.length === 0 ? (
        <>
          <div className="text-gray-500 dark:text-gray-400 space-y-2 mb-10">
            <p>
              {t('items.welcomeTitle')}
            </p>
            <p>
              {t('items.welcomeDescription')}
            </p>
          </div>
        </>
      ) : filteredItems.length === 0 && folders.length === 0 && !hasItemsInFoldersOnly && !currentFolderId ? (
        <div className="text-gray-500 dark:text-gray-400 space-y-3 mb-10">
          {/* Show filter/search-specific messages only when actively filtering or searching */}
          {(filterType !== 'all' || searchTerm) && (
            <>
              <p>
                {/* Different messages based on what's causing no results */}
                {searchTerm && filterType !== 'all'
                  // Both search and filter active
                  ? t('items.noMatchingItemsWithFilter', { filter: getFilterTitle(), search: searchTerm })
                  : searchTerm
                    // Only search active
                    ? t('items.noMatchingItemsSearch', { search: searchTerm })
                    // Only filter active (no search)
                    : filterType !== 'all'
                      ? t('items.noMatchingItems')
                      : t('items.noMatchingItems')
                }
              </p>
              {/* Clear filter/search buttons */}
              <div className="flex flex-wrap gap-2">
                {searchTerm && (
                  <button
                    onClick={() => setSearchTerm('')}
                    className="inline-flex items-center gap-1 px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg transition-colors"
                  >
                    <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                      <line x1="18" y1="6" x2="6" y2="18" />
                      <line x1="6" y1="6" x2="18" y2="18" />
                    </svg>
                    {t('items.clearSearch')}
                  </button>
                )}
                {filterType !== 'all' && (
                  <button
                    onClick={() => {
                      setItemFilterType('all');
                      localStorage.removeItem(FILTER_STORAGE_KEY);
                    }}
                    className="inline-flex items-center gap-1 px-3 py-1.5 text-sm bg-orange-100 dark:bg-orange-900/30 hover:bg-orange-200 dark:hover:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded-lg transition-colors"
                  >
                    <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                      <line x1="18" y1="6" x2="6" y2="18" />
                      <line x1="6" y1="6" x2="18" y2="18" />
                    </svg>
                    {t('items.clearFilter')}
                  </button>
                )}
              </div>
            </>
          )}
        </div>
      ) : hasItemsInFoldersOnly && filteredItems.length === 0 && !currentFolderId && !searchTerm ? (
        /* Show message when all items are in folders and we're at root level */
        <>
          {/* Folders as inline pills - only render wrapper if there are folders OR if we can create subfolders */}
          {(folders.length > 0 || canCreateSubfolder) && (
            <div className="flex flex-wrap items-center gap-2 mb-4" role="listbox" aria-label={t('items.title')}>
              {folders.map((folder, index) => (
                <FolderPill
                  key={folder.id}
                  folder={folder}
                  onClick={() => handleFolderClick(folder.id, folder.name)}
                  isActive={activeKind === 'folder' && activeIndex === index}
                  optionId={folderIdFor(index)}
                />
              ))}
              {canCreateSubfolder && (
                <button
                  onClick={handleAddFolder}
                  className="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs rounded-full transition-colors focus:outline-none text-gray-500 dark:text-gray-400 hover:text-orange-600 dark:hover:text-orange-400 hover:bg-gray-100 dark:hover:bg-gray-700/50"
                >
                  <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                  </svg>
                  <svg className="w-3 h-3 -ml-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
                    <line x1="12" y1="5" x2="12" y2="19" />
                    <line x1="5" y1="12" x2="19" y2="12" />
                  </svg>
                </button>
              )}
            </div>
          )}
          <div className="text-gray-500 dark:text-gray-400 text-sm">
            <p>{t('items.allItemsInFolders')}</p>
          </div>
        </>
      ) : (
        <>
          {/* Folders as inline pills - show when not searching */}
          {/* At root: show only if showFolders is enabled */}
          {/* Inside folder: always show subfolders and create button */}
          {/* Only render wrapper if there are folders OR if we can create subfolders */}
          {!searchTerm && (currentFolderId || showFolders) && (folders.length > 0 || canCreateSubfolder) && (
            <div className="flex flex-wrap items-center gap-2 mb-4" role="listbox" aria-label={t('items.title')}>
              {folders.map((folder, index) => (
                <FolderPill
                  key={folder.id}
                  folder={folder}
                  onClick={() => handleFolderClick(folder.id, folder.name)}
                  isActive={activeKind === 'folder' && activeIndex === index}
                  optionId={folderIdFor(index)}
                />
              ))}
              {canCreateSubfolder && (
                <button
                  onClick={handleAddFolder}
                  className={`inline-flex items-center gap-1 px-2.5 py-1.5 text-xs rounded-full transition-colors focus:outline-none ${
                    folders.length > 0
                      ? 'text-gray-500 dark:text-gray-400 hover:text-orange-600 dark:hover:text-orange-400 hover:bg-gray-100 dark:hover:bg-gray-700/50'
                      : 'text-gray-400 dark:text-gray-500 border border-dashed border-gray-300 dark:border-gray-600 hover:border-orange-400 dark:hover:border-orange-500 hover:text-orange-600 dark:hover:text-orange-400'
                  }`}
                >
                  <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                  </svg>
                  <svg className="w-3 h-3 -ml-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
                    <line x1="12" y1="5" x2="12" y2="19" />
                    <line x1="5" y1="12" x2="19" y2="12" />
                  </svg>
                  {folders.length === 0 && (
                    /**
                     * Only show text when there are no folders yet
                     * if there are folders we hide the text to save on UI space
                     */
                    <span>{t('items.newFolder')}</span>
                  )}
                </button>
              )}
            </div>
          )}

          {/* Items */}
          {sortedItems.length > 0 && (
            <ul id="items-list" role="listbox" className="space-y-2">
              {sortedItems.map((item, index) => (
                <ItemCard
                  key={item.Id}
                  item={item}
                  showFolderPath={!!searchTerm && !!item.FolderPath}
                  searchTerm={searchTerm}
                  currentFolderPath={currentFolderPath}
                  isActive={activeKind === 'item' && activeIndex === index}
                  optionId={itemIdFor(index)}
                  isHighlighted={item.Id === highlightedItemId}
                  onDuplicate={handleDuplicateItem}
                  onDelete={setDeleteItemId}
                />
              ))}
            </ul>
          )}

          {/* Show help text when inside an empty folder with no items */}
          {currentFolderId && sortedItems.length === 0 && !searchTerm && (
            <div className="text-gray-500 dark:text-gray-400 text-sm mb-4">
              <p>{t('items.emptyFolderHint')}</p>
            </div>
          )}

          {/* Clear filter/search pills at bottom of list when filtering or searching */}
          {(filterType !== 'all' || searchTerm) && (
            <div className="flex flex-wrap justify-center gap-2 mt-4 pt-4">
              {searchTerm && (
                <button
                  onClick={() => setSearchTerm('')}
                  className="inline-flex items-center gap-1 px-3 py-1.5 text-sm bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg transition-colors"
                >
                  <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                    <line x1="18" y1="6" x2="6" y2="18" />
                    <line x1="6" y1="6" x2="18" y2="18" />
                  </svg>
                  {t('items.clearSearch')}
                </button>
              )}
              {filterType !== 'all' && (
                <button
                  onClick={() => {
                    setItemFilterType('all');
                    localStorage.removeItem(FILTER_STORAGE_KEY);
                  }}
                  className="inline-flex items-center gap-1 px-3 py-1.5 text-sm bg-orange-100 dark:bg-orange-900/30 hover:bg-orange-200 dark:hover:bg-orange-900/50 text-orange-700 dark:text-orange-300 rounded-lg transition-colors"
                >
                  <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                    <line x1="18" y1="6" x2="6" y2="18" />
                    <line x1="6" y1="6" x2="18" y2="18" />
                  </svg>
                  {t('items.clearFilter')}
                </button>
              )}
            </div>
          )}
        </>
      )}

      {/* Delete Item Confirmation Modal */}
      <ConfirmDeleteModal
        isOpen={deleteItemId !== null}
        onClose={() => setDeleteItemId(null)}
        onConfirm={handleConfirmDeleteItem}
        title={t('items.deleteItemTitle')}
        message={t('items.deleteItemConfirm')}
        confirmText={t('common.delete')}
      />

      {/* Create Folder Modal */}
      <FolderModal
        isOpen={showFolderModal}
        onClose={() => setShowFolderModal(false)}
        onSave={handleSaveFolder}
        mode="create"
      />

      {/* Edit Folder Modal */}
      <FolderModal
        isOpen={showEditFolderModal}
        onClose={() => setShowEditFolderModal(false)}
        onSave={handleEditFolder}
        initialName={currentFolderName || ''}
        mode="edit"
      />

      {/* Delete Folder Modal */}
      <DeleteFolderModal
        isOpen={showDeleteFolderModal}
        onClose={() => setShowDeleteFolderModal(false)}
        onDeleteFolderOnly={handleDeleteFolderOnly}
        onDeleteFolderAndContents={handleDeleteFolderAndContents}
        itemCount={totalItemCountInFolderTree}
      />
    </div>
  );
};

export default ItemsList;
