diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv
index 9e4ab30c89ea..b0f6105f4a31 100644
--- a/config/eslint/eslint.seatbelt.tsv
+++ b/config/eslint/eslint.seatbelt.tsv
@@ -225,6 +225,7 @@
 "../../src/hooks/useNewTransactions.ts"	"react-hooks/refs"	2
 "../../src/hooks/useOutstandingBalanceGuard.tsx"	"@typescript-eslint/no-deprecated/ConfirmModal"	1
 "../../src/hooks/usePaymentOptions.ts"	"react-hooks/refs"	1
+"../../src/hooks/usePersonalDetailSearchSelector/index.native.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	1
 "../../src/hooks/usePrevious.ts"	"react-hooks/refs"	1
 "../../src/hooks/useProactiveAppReview.ts"	"react-hooks/purity"	1
 "../../src/hooks/useRestoreInputFocus/index.android.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	1
@@ -234,7 +235,7 @@
 "../../src/hooks/useSearchBulkActions.ts"	"react-hooks/preserve-manual-memoization"	2
 "../../src/hooks/useSearchBulkActions.ts"	"react-hooks/refs"	1
 "../../src/hooks/useSearchHighlightAndScroll.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	1
-"../../src/hooks/useSearchSelector.native.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	1
+"../../src/hooks/useSearchSelector/index.native.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	1
 "../../src/hooks/useSelectionModeReportActions.ts"	"@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions"	2
 "../../src/hooks/useSidebarOrderedReports.tsx"	"react-hooks/purity"	1
 "../../src/hooks/useSidebarOrderedReports.tsx"	"react-hooks/refs"	5
diff --git a/src/components/Search/FilterComponents/UserSelector.tsx b/src/components/Search/FilterComponents/UserSelector.tsx
index f3f5d59b1a7c..c153f4428781 100644
--- a/src/components/Search/FilterComponents/UserSelector.tsx
+++ b/src/components/Search/FilterComponents/UserSelector.tsx
@@ -1,18 +1,14 @@
-import isEmpty from 'lodash/isEmpty';
-import React, {useRef, useState} from 'react';
+import React, {useRef} from 'react';
 import {usePersonalDetails} from '@components/OnyxListItemProvider';
 import SelectionList from '@components/SelectionList';
 import UserSelectionListItem from '@components/SelectionList/ListItem/UserSelectionListItem';
 import type {ListItem, SelectionListHandle} from '@components/SelectionList/types';
-import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
-import useSearchSelector from '@hooks/useSearchSelector';
+import usePersonalDetailSearchSelector from '@hooks/usePersonalDetailSearchSelector';
 import useThemeStyles from '@hooks/useThemeStyles';
 import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
-import {getParticipantsOption} from '@libs/OptionsListUtils';
-import {doesPersonalDetailMatchSearchTerm} from '@libs/OptionsListUtils/searchMatchUtils';
-import type {OptionData} from '@libs/ReportUtils';
+import type {OptionData} from '@libs/PersonalDetailOptionsListUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ListFilterWrapper from './ListFilterViewWrapper';
@@ -27,102 +23,39 @@ function UserSelector({value = [], onChange}: UserSelectorProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const personalDetails = usePersonalDetails();
-    const currentUserPersonalDetails = useCurrentUserPersonalDetails();
-    const currentUserAccountID = currentUserPersonalDetails.accountID;
     const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
     const [isSearchingForReports] = useOnyx(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS);
-    const initialSelectedOptions = value.reduce<OptionData[]>((options, id) => {
+    const initialSelectedAccountIDs = value.reduce<Set<string>>((acc, id) => {
         const participant = personalDetails?.[id];
         if (!participant) {
-            return options;
+            return acc;
         }
 
-        const optionData = {
-            ...getParticipantsOption(participant, personalDetails),
-            isSelected: true,
-        };
+        acc.add(id);
+        return acc;
+    }, new Set<string>());
 
-        if (optionData) {
-            options.push(optionData as OptionData);
-        }
-
-        return options;
-    }, []);
-
-    const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, toggleSelection, areOptionsInitialized, selectedOptionsForDisplay, onListEndReached} = useSearchSelector({
+    const {searchTerm, setSearchTerm, availableOptions, totalOptionsCount, toggleSelection, areOptionsInitialized} = usePersonalDetailSearchSelector({
         selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI,
-        searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL,
-        initialSelected: initialSelectedOptions,
+        initialSelected: initialSelectedAccountIDs,
         excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
-        maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
-        includeUserToInvite: false,
-        includeCurrentUser: true,
-        onSelectionChange: (options) => onChange(options.flatMap((option) => (option.accountID ? [option.accountID.toString()] : []))),
+        includeCurrentUser: false,
+        includeRecentReports: false,
+        onSelectionChange: onChange,
     });
 
     const listData = (() => {
-        const personalDetailsList = availableOptions.personalDetails.map((participant) => ({
-            ...participant,
-            keyForList: String(participant.accountID),
-        }));
-        const recentReports = availableOptions.recentReports.map((report) => ({
-            ...report,
-            keyForList: String(report.reportID),
-        }));
-
-        const isCurrentUserSelected = selectedOptionsForDisplay.some((option) => option.accountID === currentUserAccountID);
-
-        // Extract the current user from available options to guarantee they appear at the top.
-        // Falls back to creating from personal details to handle pagination edge cases.
-        let currentUserOption: OptionData | undefined;
-        if (!isCurrentUserSelected && currentUserAccountID) {
-            const currentUserPersonalDetail = personalDetailsList.find((p) => p.accountID === currentUserAccountID) ?? recentReports.find((r) => r.accountID === currentUserAccountID);
-            if (currentUserPersonalDetail) {
-                currentUserOption = currentUserPersonalDetail;
-            } else if (personalDetails?.[currentUserAccountID]) {
-                const candidateOption = getParticipantsOption(personalDetails[currentUserAccountID], personalDetails) as OptionData;
-                const trimmedSearchTerm = debouncedSearchTerm.trim().toLowerCase();
-                if (!trimmedSearchTerm || doesPersonalDetailMatchSearchTerm(candidateOption, currentUserAccountID, trimmedSearchTerm)) {
-                    currentUserOption = candidateOption;
-                }
-            }
+        if (!availableOptions.currentUserOption) {
+            return [...availableOptions.selectedOptions, ...availableOptions.personalDetails];
         }
-
-        // Filter current user from regular lists to avoid duplication
-        const filteredPersonalDetails = currentUserOption ? personalDetailsList.filter((p) => p.accountID !== currentUserAccountID) : personalDetailsList;
-        const filteredRecentReports = currentUserOption ? recentReports.filter((r) => r.accountID !== currentUserAccountID) : recentReports;
-
-        // Place selected options first, then the current user, then the rest
-        const combinedOptions = [...selectedOptionsForDisplay, ...(currentUserOption ? [currentUserOption] : []), ...filteredPersonalDetails, ...filteredRecentReports];
-
-        // Sort so that selected items appear first; current user placement is handled explicitly above
-        combinedOptions.sort((a, b) => {
-            if (a.isSelected && !b.isSelected) {
-                return -1;
-            }
-            if (!a.isSelected && b.isSelected) {
-                return 1;
-            }
-            // Among selected items, prioritize the current user
-            if (a.isSelected && b.isSelected) {
-                if (a.accountID === currentUserAccountID) {
-                    return -1;
-                }
-                if (b.accountID === currentUserAccountID) {
-                    return 1;
-                }
-            }
-            return 0;
-        });
-
-        const combinedOptionsWithKeyForList = combinedOptions.map((option) => ({
-            ...option,
-            keyForList: option.keyForList ?? option.login ?? '',
-        }));
-        return combinedOptionsWithKeyForList;
+        const isCurrentOptionSelected = availableOptions.currentUserOption.isSelected;
+        if (isCurrentOptionSelected) {
+            return [availableOptions.currentUserOption, ...availableOptions.selectedOptions, ...availableOptions.personalDetails];
+        }
+        return [...availableOptions.selectedOptions, availableOptions.currentUserOption, ...availableOptions.personalDetails];
     })();
 
-    const headerMessage = isEmpty(listData) ? translate('common.noResultsFound') : undefined;
+    const headerMessage = listData.length === 0 ? translate('common.noResultsFound') : undefined;
 
     const selectUser = (option: OptionData) => {
         toggleSelection(option);
@@ -130,13 +63,6 @@ function UserSelector({value = [], onChange}: UserSelectorProps) {
     };
 
     const isLoadingNewOptions = !!isSearchingForReports;
-    const totalOptions = selectedOptionsForDisplay.length + availableOptions.personalDetails.length + availableOptions.recentReports.length;
-    const [totalOptionsCount, setTotalOptionsCount] = useState(totalOptions);
-
-    if (totalOptions !== totalOptionsCount && !debouncedSearchTerm) {
-        setTotalOptionsCount(selectedOptionsForDisplay.length + availableOptions.personalDetails.length + availableOptions.recentReports.length);
-    }
-
     const shouldShowSearchInput = totalOptionsCount >= CONST.STANDARD_LIST_ITEM_LIMIT;
 
     const textInputOptions = shouldShowSearchInput
@@ -163,7 +89,6 @@ function UserSelector({value = [], onChange}: UserSelectorProps) {
                 onSelectRow={selectUser}
                 isLoadingNewOptions={isLoadingNewOptions}
                 shouldShowLoadingPlaceholder={!areOptionsInitialized}
-                onEndReached={onListEndReached}
                 style={{contentContainerStyle: [styles.pb0]}}
             />
         </ListFilterWrapper>
diff --git a/src/hooks/useContactImport.ts b/src/hooks/useContactImport.ts
index e505a3bac2b8..0fc82abfc2a2 100644
--- a/src/hooks/useContactImport.ts
+++ b/src/hooks/useContactImport.ts
@@ -1,16 +1,14 @@
 import {useCallback, useState} from 'react';
 import {RESULTS} from 'react-native-permissions';
 import type {PermissionStatus} from 'react-native-permissions';
-import {usePersonalDetails} from '@components/OnyxListItemProvider';
 import contactImport from '@libs/ContactImport';
 import type {ContactImportResult} from '@libs/ContactImport/types';
 import useContactPermissions from '@libs/ContactPermission/useContactPermissions';
-import getContacts from '@libs/ContactUtils';
+import {getContactsExtended} from '@libs/ContactUtils';
 import type {SearchOption} from '@libs/OptionsListUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {PersonalDetails} from '@src/types/onyx';
-import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
 import useLocalize from './useLocalize';
 import useOnyx from './useOnyx';
 
@@ -32,20 +30,17 @@ type UseContactImportResult = {
 function useContactImport(): UseContactImportResult {
     const [contactPermissionState, setContactPermissionState] = useState<PermissionStatus>(RESULTS.UNAVAILABLE);
     const [contacts, setContacts] = useState<Array<SearchOption<PersonalDetails>>>([]);
-    const {localeCompare} = useLocalize();
+    const {localeCompare, formatPhoneNumber} = useLocalize();
     const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);
     const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
-    const personalDetails = usePersonalDetails();
-    const currentUserPersonalDetails = useCurrentUserPersonalDetails();
-    const currentUserEmail = currentUserPersonalDetails.email ?? '';
 
     const importAndSaveContacts = useCallback(() => {
         contactImport().then(({contactList, permissionStatus}: ContactImportResult) => {
             setContactPermissionState(permissionStatus);
-            const usersFromContact = getContacts(contactList, localeCompare, countryCode, loginList, currentUserEmail, personalDetails);
+            const usersFromContact = getContactsExtended(contactList, localeCompare, formatPhoneNumber, countryCode, loginList);
             setContacts(usersFromContact);
         });
-    }, [localeCompare, countryCode, loginList, currentUserEmail, personalDetails]);
+    }, [localeCompare, formatPhoneNumber, countryCode, loginList]);
 
     useContactPermissions({
         importAndSaveContacts,
diff --git a/src/hooks/useContactImportNew.ts b/src/hooks/useContactImportNew.ts
new file mode 100644
index 000000000000..5b38b1d34074
--- /dev/null
+++ b/src/hooks/useContactImportNew.ts
@@ -0,0 +1,59 @@
+import {useState} from 'react';
+import {RESULTS} from 'react-native-permissions';
+import type {PermissionStatus} from 'react-native-permissions';
+import contactImport from '@libs/ContactImport';
+import type {ContactImportResult} from '@libs/ContactImport/types';
+import useContactPermissions from '@libs/ContactPermission/useContactPermissions';
+import {getContacts} from '@libs/ContactUtils';
+import type {OptionData} from '@libs/PersonalDetailOptionsListUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import useLocalize from './useLocalize';
+import useOnyx from './useOnyx';
+
+/**
+ * Return type of the useContactImport hook.
+ */
+type UseContactImportResult = {
+    contacts: OptionData[];
+    contactPermissionState: PermissionStatus;
+    importAndSaveContacts: () => void;
+    setContactPermissionState: React.Dispatch<React.SetStateAction<PermissionStatus>>;
+};
+
+/**
+ * Custom hook that handles importing device contacts,
+ * managing permissions, and transforming contact data
+ * into a format suitable for use in the app.
+ */
+function useContactImport(): UseContactImportResult {
+    const [contactPermissionState, setContactPermissionState] = useState<PermissionStatus>(RESULTS.UNAVAILABLE);
+    const [contacts, setContacts] = useState<OptionData[]>([]);
+    const {localeCompare, formatPhoneNumber} = useLocalize();
+    const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);
+    const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
+
+    const importAndSaveContacts = () => {
+        contactImport().then(({contactList, permissionStatus}: ContactImportResult) => {
+            setContactPermissionState(permissionStatus);
+            const usersFromContact = getContacts(contactList, localeCompare, formatPhoneNumber, countryCode, loginList);
+            setContacts(usersFromContact);
+        });
+    };
+
+    useContactPermissions({
+        importAndSaveContacts,
+        setContacts,
+        contactPermissionState,
+        setContactPermissionState,
+    });
+
+    return {
+        contacts,
+        contactPermissionState,
+        importAndSaveContacts,
+        setContactPermissionState,
+    };
+}
+
+export default useContactImport;
diff --git a/src/hooks/usePersonalDetailSearchSelector/base.ts b/src/hooks/usePersonalDetailSearchSelector/base.ts
new file mode 100644
index 000000000000..10b22dfe2fc9
--- /dev/null
+++ b/src/hooks/usePersonalDetailSearchSelector/base.ts
@@ -0,0 +1,288 @@
+import {useState} from 'react';
+import type {PermissionStatus} from 'react-native-permissions';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useDebouncedState from '@hooks/useDebouncedState';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import usePersonalDetailOptions from '@hooks/usePersonalDetailOptions';
+import {filterOption, getValidOptions} from '@libs/PersonalDetailOptionsListUtils';
+import type {OptionData} from '@libs/PersonalDetailOptionsListUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+
+type SearchSelectorSelectionMode = (typeof CONST.SEARCH_SELECTOR)[keyof Pick<typeof CONST.SEARCH_SELECTOR, 'SELECTION_MODE_SINGLE' | 'SELECTION_MODE_MULTI'>];
+
+type UseSearchSelectorConfig = {
+    /** Selection mode - single or multiple selection */
+    selectionMode: SearchSelectorSelectionMode;
+
+    /** How many recent reports should be returned? The rest count from maxResultsPerPage will be with contacts. null value means CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW */
+    maxRecentReportsToShow?: number;
+
+    /** Max number of options to return in search results (including recent reports and personal details). null value means no limit */
+    maxElements?: number;
+
+    /** Whether to include user to invite option */
+    includeUserToInvite?: boolean;
+
+    /** Logins to exclude from results (hard exclusions - cannot be selected at all) */
+    excludeLogins?: Record<string, boolean>;
+
+    /** Logins to exclude from suggestions only (soft exclusions - can still be manually entered) */
+    excludeFromSuggestionsOnly?: Record<string, boolean>;
+
+    /** Whether to include recent reports (for getMemberInviteOptions) */
+    includeRecentReports?: boolean;
+
+    /** Whether to include current user */
+    includeCurrentUser?: boolean;
+
+    /** Whether to include domain emails */
+    includeDomainEmail?: boolean;
+
+    /** Enable phone contacts integration */
+    enablePhoneContacts?: boolean;
+
+    /** Callback when selection changes (multi-select mode) */
+    onSelectionChange?: (selected: string[]) => void;
+
+    /** Callback when single option is selected (single-select mode) */
+    onSingleSelect?: (option: OptionData) => void;
+
+    /** Initial selected options */
+    initialSelected?: Set<string>;
+
+    /** Whether to initialize the hook */
+    shouldInitialize?: boolean;
+
+    /** Additional contact options to merge (used by platform-specific implementations) */
+    contactOptions?: OptionData[];
+
+    /** Whether to filter with recent attendees */
+    recentAttendees?: string[];
+
+    /** Whether to allow name-only options */
+    shouldAllowNameOnlyOptions?: boolean;
+
+    /** Whether to keep selected options in availableOptions instead of filtering them out */
+    shouldKeepSelectedInAvailableOptions?: boolean;
+
+    /** Initial Search Phrase */
+    initialSearchPhrase?: string;
+};
+
+type ContactState = {
+    /** Current permission status */
+    permissionStatus: PermissionStatus;
+
+    /** Whether to show import UI */
+    showImportUI: boolean;
+
+    /** Function to trigger contact import */
+    importContacts: () => void;
+
+    /** Function to initiate contact import and set state */
+    initiateContactImportAndSetState: () => void;
+
+    /** Function to set permission state */
+    setContactPermissionState: (status: PermissionStatus) => void;
+};
+
+type AvailableOptions = {
+    selectedOptions: OptionData[];
+    recentOptions: OptionData[];
+    personalDetails: OptionData[];
+    userToInvite: OptionData | null;
+    extraOptions: OptionData[];
+    currentUserOption: OptionData | null;
+};
+
+type UseSearchSelectorReturn = {
+    /** Current search term */
+    searchTerm: string;
+
+    /** Debounced search term */
+    debouncedSearchTerm: string;
+
+    /** Function to update search term */
+    setSearchTerm: (value: string) => void;
+
+    /** Currently selected options */
+    selectedOptions: OptionData[];
+
+    /** Available (unselected) options */
+    availableOptions: AvailableOptions;
+
+    /* Total count of options (without filters) */
+    totalOptionsCount: number;
+
+    /** Function to toggle selection state of an option */
+    toggleSelection: (option: OptionData) => void;
+
+    /** Function to reset selection state of an option */
+    resetSelection: () => void;
+
+    /** Whether options are initialized */
+    areOptionsInitialized: boolean;
+
+    /** Contact-related state and functions (when enablePhoneContacts is true) */
+    contactState?: ContactState;
+};
+
+const defaultListOptions = {
+    userToInvite: null,
+    recentOptions: [],
+    personalDetails: [],
+    selectedOptions: [],
+};
+
+/**
+ * Base hook that provides search functionality with selection logic for option lists.
+ * This contains the core logic without platform-specific dependencies.
+ */
+function usePersonalDetailSearchSelectorBase({
+    selectionMode,
+    maxElements,
+    maxRecentReportsToShow,
+    includeUserToInvite = false,
+    includeDomainEmail = false,
+    excludeLogins = CONST.EMPTY_OBJECT,
+    excludeFromSuggestionsOnly = CONST.EMPTY_OBJECT,
+    includeRecentReports = true,
+    onSelectionChange,
+    onSingleSelect,
+    initialSelected = new Set<string>(),
+    shouldInitialize = true,
+    contactOptions,
+    includeCurrentUser = false,
+    recentAttendees,
+    shouldAllowNameOnlyOptions = false,
+    shouldKeepSelectedInAvailableOptions = false,
+    initialSearchPhrase = '',
+}: UseSearchSelectorConfig): UseSearchSelectorReturn {
+    const {translate, formatPhoneNumber} = useLocalize();
+    const {options: defaultOptions, currentOption} = usePersonalDetailOptions({enabled: shouldInitialize});
+
+    const optionsWithContacts = (() => {
+        if (!contactOptions?.length || !shouldInitialize) {
+            return defaultOptions;
+        }
+        return (defaultOptions ?? []).concat(contactOptions);
+    })();
+    const areOptionsInitialized = (optionsWithContacts?.length ?? 0) > 0;
+    const [selectedAccountIDs, setSelectedAccountIDs] = useState<Set<string>>(initialSelected);
+    const [extraOptions, setExtraOptions] = useState<OptionData[]>([]);
+    const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(initialSearchPhrase);
+    const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);
+    const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
+    const currentUserPersonalDetails = useCurrentUserPersonalDetails();
+    const currentUserEmail = currentUserPersonalDetails.email ?? '';
+
+    const transformedOptions: OptionData[] =
+        optionsWithContacts?.map((option) => ({
+            ...option,
+            isSelected: selectedAccountIDs.has(option.accountID.toString()),
+        })) ?? [];
+
+    const selectedOptions = (() => {
+        const options: OptionData[] = [];
+        for (const option of transformedOptions) {
+            if (option.isSelected) {
+                options.push(option);
+            }
+        }
+        for (const option of extraOptions) {
+            if (option.isSelected) {
+                options.push(option);
+            }
+        }
+        return options;
+    })();
+
+    const optionsList = !areOptionsInitialized
+        ? defaultListOptions
+        : getValidOptions(transformedOptions, currentUserEmail, formatPhoneNumber, countryCode, loginList, {
+              excludeLogins,
+              excludeFromSuggestionsOnly,
+              includeSelectedOptions: shouldKeepSelectedInAvailableOptions,
+              includeRecentReports,
+              recentAttendees,
+              searchString: debouncedSearchTerm,
+              maxElements,
+              recentMaxElements: maxRecentReportsToShow,
+              includeUserToInvite,
+              includeCurrentUser,
+              includeDomainEmail,
+              extraOptions,
+              shouldAcceptName: shouldAllowNameOnlyOptions,
+          });
+
+    const currentUserSearchTerms = [translate('common.you'), translate('common.me')];
+    const filteredCurrentUserOption = (() => {
+        const newOption = filterOption(currentOption, debouncedSearchTerm, currentUserSearchTerms);
+        if (newOption) {
+            return {
+                ...newOption,
+                isSelected: selectedAccountIDs.has(newOption.accountID.toString()),
+            };
+        }
+        return newOption;
+    })();
+
+    const existingAccountIDs = new Set(optionsWithContacts?.map((option) => option.accountID.toString()));
+
+    /**
+     * Toggle selection state of option based on selection mode
+     */
+    const toggleSelection = (option: OptionData) => {
+        if (selectionMode === CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE) {
+            onSingleSelect?.(option);
+            return;
+        }
+
+        const isSelected = selectedAccountIDs.has(option.accountID.toString());
+
+        if (isSelected) {
+            // If the option is selected, remove it from the selected logins
+            const isInExtraOption = extraOptions.some((extraOption) => extraOption.accountID === option.accountID);
+            if (isInExtraOption) {
+                setExtraOptions((prev) => prev.filter((extraOption) => extraOption.accountID !== option.accountID));
+            }
+            const newSet = new Set([...selectedAccountIDs].filter((accountID) => accountID !== option.accountID.toString()));
+            setSelectedAccountIDs(newSet);
+            onSelectionChange?.(Array.from(newSet));
+        } else {
+            const newSet = new Set(selectedAccountIDs).add(option.accountID.toString());
+            setSelectedAccountIDs(newSet);
+            onSelectionChange?.(Array.from(newSet));
+            if (!existingAccountIDs.has(option.accountID.toString())) {
+                setExtraOptions((prev) => [...prev, {...option, isSelected: true}]);
+            }
+        }
+    };
+
+    const resetSelection = () => {
+        setExtraOptions([]);
+        setSelectedAccountIDs(new Set());
+    };
+
+    return {
+        searchTerm,
+        debouncedSearchTerm,
+        setSearchTerm,
+        selectedOptions,
+        availableOptions: {
+            ...optionsList,
+            currentUserOption: filteredCurrentUserOption,
+            extraOptions,
+        },
+        totalOptionsCount: optionsWithContacts?.length ?? 0,
+        toggleSelection,
+        resetSelection,
+        areOptionsInitialized,
+        contactState: undefined,
+    };
+}
+
+export default usePersonalDetailSearchSelectorBase;
+export type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn, SearchSelectorSelectionMode};
diff --git a/src/hooks/usePersonalDetailSearchSelector/index.native.ts b/src/hooks/usePersonalDetailSearchSelector/index.native.ts
new file mode 100644
index 000000000000..9554fbf7a4e1
--- /dev/null
+++ b/src/hooks/usePersonalDetailSearchSelector/index.native.ts
@@ -0,0 +1,52 @@
+// eslint-disable-next-line no-restricted-imports
+import {InteractionManager} from 'react-native';
+import {RESULTS} from 'react-native-permissions';
+import useContactImport from '@hooks/useContactImportNew';
+import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './base';
+import usePersonalDetailSearchSelectorBase from './base';
+
+/**
+ * Hook that combines search functionality with selection logic for option lists.
+ * Leverages heap optimization for performance with large datasets.
+ * Native version includes phone contacts integration.
+ *
+ * @param config - Configuration object for the hook
+ * @returns Object with search and selection utilities
+ */
+function usePersonalDetailSearchSelector(config: UseSearchSelectorConfig): UseSearchSelectorReturn {
+    const {enablePhoneContacts = false} = config;
+
+    // Phone contacts logic
+    const {contacts, contactPermissionState, importAndSaveContacts, setContactPermissionState} = useContactImport();
+    const showImportContacts = enablePhoneContacts && !(contactPermissionState === RESULTS.GRANTED || contactPermissionState === RESULTS.LIMITED);
+
+    const initiateContactImportAndSetState = () => {
+        setContactPermissionState(RESULTS.GRANTED);
+        InteractionManager.runAfterInteractions(importAndSaveContacts);
+    };
+
+    // Use base hook with contact options
+    const baseResult = usePersonalDetailSearchSelectorBase({
+        ...config,
+        contactOptions: enablePhoneContacts ? contacts : undefined,
+    });
+
+    // Build contact state if enabled
+    const contactState: ContactState | undefined = enablePhoneContacts
+        ? {
+              permissionStatus: contactPermissionState,
+              showImportUI: showImportContacts,
+              importContacts: importAndSaveContacts,
+              initiateContactImportAndSetState,
+              setContactPermissionState,
+          }
+        : undefined;
+
+    return {
+        ...baseResult,
+        contactState,
+    };
+}
+
+export default usePersonalDetailSearchSelector;
+export type {ContactState};
diff --git a/src/hooks/usePersonalDetailSearchSelector/index.ts b/src/hooks/usePersonalDetailSearchSelector/index.ts
new file mode 100644
index 000000000000..248ce60aaf36
--- /dev/null
+++ b/src/hooks/usePersonalDetailSearchSelector/index.ts
@@ -0,0 +1,17 @@
+import usePersonalDetailSearchSelectorBase from './base';
+import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './base';
+
+/**
+ * Hook that combines search functionality with selection logic for option lists.
+ * Leverages heap optimization for performance with large datasets.
+ * Web version without phone contacts integration.
+ *
+ * @param config - Configuration object for the hook
+ * @returns Object with search and selection utilities
+ */
+function usePersonalDetailSearchSelector(config: UseSearchSelectorConfig): UseSearchSelectorReturn {
+    return usePersonalDetailSearchSelectorBase(config);
+}
+
+export default usePersonalDetailSearchSelector;
+export type {ContactState};
diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector/base.ts
similarity index 98%
rename from src/hooks/useSearchSelector.base.ts
rename to src/hooks/useSearchSelector/base.ts
index f56a2acc4ffa..189e049d901a 100644
--- a/src/hooks/useSearchSelector.base.ts
+++ b/src/hooks/useSearchSelector/base.ts
@@ -3,6 +3,11 @@ import {useState} from 'react';
 import type {PermissionStatus} from 'react-native-permissions';
 import {usePersonalDetails} from '@components/OnyxListItemProvider';
 import {useOptionsList} from '@components/OptionListContextProvider';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
+import useDebounce from '@hooks/useDebounce';
+import useDebouncedState from '@hooks/useDebouncedState';
+import useOnyx from '@hooks/useOnyx';
+import useSortedActions from '@hooks/useSortedActions';
 import type {GetOptionsConfig, Option, Options, SearchOption} from '@libs/OptionsListUtils';
 import {getEmptyOptions, getSearchOptions, getSearchValueForPhoneOrEmail, getValidOptions} from '@libs/OptionsListUtils';
 import {getPersonalDetailSearchTerms} from '@libs/OptionsListUtils/searchMatchUtils';
@@ -10,11 +15,6 @@ import type {OptionData} from '@libs/ReportUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
-import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
-import useDebounce from './useDebounce';
-import useDebouncedState from './useDebouncedState';
-import useOnyx from './useOnyx';
-import useSortedActions from './useSortedActions';
 
 type SearchSelectorContext = (typeof CONST.SEARCH_SELECTOR)[keyof Pick<
     typeof CONST.SEARCH_SELECTOR,
diff --git a/src/hooks/useSearchSelector.native.ts b/src/hooks/useSearchSelector/index.native.ts
similarity index 93%
rename from src/hooks/useSearchSelector.native.ts
rename to src/hooks/useSearchSelector/index.native.ts
index 221f47595aff..1158051fdb9e 100644
--- a/src/hooks/useSearchSelector.native.ts
+++ b/src/hooks/useSearchSelector/index.native.ts
@@ -2,9 +2,9 @@ import {useCallback, useMemo} from 'react';
 // eslint-disable-next-line no-restricted-imports
 import {InteractionManager} from 'react-native';
 import {RESULTS} from 'react-native-permissions';
-import useContactImport from './useContactImport';
-import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './useSearchSelector.base';
-import useSearchSelectorBase from './useSearchSelector.base';
+import useContactImport from '@hooks/useContactImport';
+import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './base';
+import useSearchSelectorBase from './base';
 
 /**
  * Hook that combines search functionality with selection logic for option lists.
diff --git a/src/hooks/useSearchSelector.ts b/src/hooks/useSearchSelector/index.ts
similarity index 84%
rename from src/hooks/useSearchSelector.ts
rename to src/hooks/useSearchSelector/index.ts
index e2db08890704..8e0cf0242810 100644
--- a/src/hooks/useSearchSelector.ts
+++ b/src/hooks/useSearchSelector/index.ts
@@ -1,5 +1,5 @@
-import useSearchSelectorBase from './useSearchSelector.base';
-import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './useSearchSelector.base';
+import useSearchSelectorBase from './base';
+import type {ContactState, UseSearchSelectorConfig, UseSearchSelectorReturn} from './base';
 
 /**
  * Hook that combines search functionality with selection logic for option lists.
diff --git a/src/libs/ContactUtils.ts b/src/libs/ContactUtils.ts
index 66c79de8232d..91d7f7336fa6 100644
--- a/src/libs/ContactUtils.ts
+++ b/src/libs/ContactUtils.ts
@@ -1,10 +1,11 @@
 import type {OnyxEntry} from 'react-native-onyx';
 import type {LocaleContextProps} from '@components/LocaleContextProvider';
 import CONST from '@src/CONST';
-import type {Login, PersonalDetails, PersonalDetailsList} from '@src/types/onyx';
+import type {LoginList, PersonalDetails} from '@src/types/onyx';
 import type {DeviceContact, StringHolder} from './ContactImport/types';
-import {getUserToInviteContactOption} from './OptionsListUtils';
 import type {SearchOption} from './OptionsListUtils';
+import {getContactOption} from './PersonalDetailOptionsListUtils';
+import type {OptionData} from './PersonalDetailOptionsListUtils';
 import RandomAvatarUtils from './RandomAvatarUtils';
 
 function sortEmailObjects(emails: StringHolder[], localeCompare: LocaleContextProps['localeCompare']): string[] {
@@ -29,14 +30,13 @@ function sortEmailObjects(emails: StringHolder[], localeCompare: LocaleContextPr
     });
 }
 
-const getContacts = (
+function getContacts(
     deviceContacts: DeviceContact[] | [],
     localeCompare: LocaleContextProps['localeCompare'],
+    formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
     countryCode: number,
-    loginList: OnyxEntry<Login>,
-    currentUserEmail: string,
-    personalDetails: OnyxEntry<PersonalDetailsList>,
-): Array<SearchOption<PersonalDetails>> => {
+    loginList: OnyxEntry<LoginList>,
+): OptionData[] {
     return deviceContacts
         .map((contact) => {
             const email = sortEmailObjects(contact?.emailAddresses ?? [], localeCompare)?.at(0) ?? '';
@@ -46,9 +46,7 @@ const getContacts = (
             const firstName = contact?.firstName ?? '';
             const lastName = contact?.lastName ?? '';
 
-            return getUserToInviteContactOption({
-                selectedOptions: [],
-                optionsToExclude: [],
+            return getContactOption({
                 searchValue: email || phoneNumber || firstName || '',
                 firstName,
                 lastName,
@@ -56,13 +54,49 @@ const getContacts = (
                 phone: phoneNumber,
                 avatar: avatarSource,
                 countryCode,
+                formatPhoneNumber,
                 loginList,
-                currentUserEmail,
-                personalDetails,
             });
         })
-        .filter((contact): contact is SearchOption<PersonalDetails> => contact !== null);
-};
+        .filter((contact): contact is OptionData => contact !== null);
+}
+
+function extendPersonalDetailOption(option: OptionData): SearchOption<PersonalDetails> {
+    const userDetails = {
+        accountID: option.accountID,
+        avatar: option.icons?.[0].source,
+        displayName: option.text,
+        login: option.login,
+        pronouns: '',
+        phoneNumber: option.phoneNumber,
+        validated: true,
+    };
+
+    return {
+        ...option,
+        // eslint-disable-next-line rulesdir/no-default-id-values
+        reportID: option.reportID ?? '',
+        item: userDetails,
+        participantsList: [userDetails],
+        isDefaultRoom: false,
+        isPinned: false,
+        isChatRoom: false,
+        shouldShowSubscript: false,
+        isPolicyExpenseChat: false,
+        lastMessageText: '',
+        isBold: true,
+    };
+}
+
+function getContactsExtended(
+    deviceContacts: DeviceContact[] | [],
+    localeCompare: LocaleContextProps['localeCompare'],
+    formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
+    countryCode: number,
+    loginList: OnyxEntry<LoginList>,
+): Array<SearchOption<PersonalDetails>> {
+    const contactOptions = getContacts(deviceContacts, localeCompare, formatPhoneNumber, countryCode, loginList);
+    return contactOptions.map(extendPersonalDetailOption);
+}
 
-export default getContacts;
-export {sortEmailObjects};
+export {getContacts, getContactsExtended, sortEmailObjects};
diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts
index 99b9600ab149..9ba81eb30eb1 100644
--- a/src/libs/OptionsListUtils/index.ts
+++ b/src/libs/OptionsListUtils/index.ts
@@ -2007,109 +2007,6 @@ function getUserToInviteOption({
     return userToInvite;
 }
 
-function getUserToInviteContactOption({
-    searchValue = '',
-    optionsToExclude = [],
-    selectedOptions = [],
-    firstName = '',
-    lastName = '',
-    email = '',
-    phone = '',
-    avatar = '',
-    countryCode = CONST.DEFAULT_COUNTRY_CODE,
-    loginList = {},
-    currentUserEmail,
-}: GetUserToInviteConfig): SearchOption<PersonalDetails> | null {
-    // If email is provided, use it as the primary identifier
-
-    const effectiveSearchValue = email || searchValue;
-
-    // Handle phone number parsing for either provided phone or searchValue
-
-    const phoneToCheck = phone || searchValue;
-    const normalizedPhoneNumber = appendCountryCode(Str.removeSMSDomain(phoneToCheck), countryCode);
-    const parsedPhoneNumber = parsePhoneNumber(normalizedPhoneNumber);
-
-    // Validate email (either provided email or searchValue)
-    const isValidEmail = Str.isValidEmail(effectiveSearchValue) && !Str.isDomainEmail(effectiveSearchValue) && !Str.endsWith(effectiveSearchValue, CONST.SMS.DOMAIN);
-
-    const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? ''));
-
-    const sanitizedPhoneLogin = isValidPhoneNumber ? addSMSDomainIfPhoneNumber(parsedPhoneNumber.number?.e164 ?? normalizedPhoneNumber) : '';
-    const login = email ? effectiveSearchValue : (sanitizedPhoneLogin ?? searchValue);
-    const normalizedLoginToExclude = addSMSDomainIfPhoneNumber(login).toLowerCase();
-
-    const isCurrentUserLogin = isCurrentUser({login} as PersonalDetails, loginList, currentUserEmail);
-    const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === login);
-
-    const isInOptionToExclude = optionsToExclude.findIndex((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === normalizedLoginToExclude) !== -1;
-
-    if (!effectiveSearchValue || isCurrentUserLogin || isInSelectedOption || (!isValidEmail && !isValidPhoneNumber) || isInOptionToExclude) {
-        return null;
-    }
-
-    // Generates an optimistic account ID for new users not yet saved in Onyx
-    const optimisticAccountID = generateAccountID(login);
-
-    // Construct display name if firstName/lastName are provided
-
-    const displayName = firstName && lastName ? `${firstName} ${lastName}` : firstName || lastName || effectiveSearchValue;
-
-    // Create the base user details that will be used in both item and participantsList
-    const userDetails = {
-        accountID: optimisticAccountID,
-
-        avatar: avatar || FallbackAvatar,
-        firstName: firstName ?? '',
-        lastName: lastName ?? '',
-        displayName,
-        login,
-        pronouns: '',
-        phoneNumber: phone ?? '',
-        validated: true,
-    };
-
-    const userToInvite = {
-        item: userDetails,
-        text: displayName,
-        displayName,
-        firstName,
-        lastName,
-        alternateText: displayName !== login ? login : undefined,
-        brickRoadIndicator: null,
-        icons: [
-            {
-                source: userDetails.avatar,
-                type: CONST.ICON_TYPE_AVATAR,
-                name: login,
-                id: optimisticAccountID,
-            },
-        ],
-        tooltipText: null,
-        participantsList: [userDetails],
-        accountID: optimisticAccountID,
-        login,
-        reportID: '',
-        phoneNumber: phone ?? '',
-        hasDraftComment: false,
-        keyForList: optimisticAccountID.toString(),
-        isDefaultRoom: false,
-        isPinned: false,
-        isWaitingOnBankAccount: false,
-        isIOUReportOwner: false,
-        iouReportAmount: 0,
-        isChatRoom: false,
-        shouldShowSubscript: false,
-        isPolicyExpenseChat: false,
-        isExpenseReport: false,
-        lastMessageText: '',
-        isBold: true,
-        isOptimisticAccount: true,
-    };
-
-    return userToInvite;
-}
-
 function isValidReport(option: SearchOption<Report>, policy: OnyxEntry<Policy>, config: IsValidReportsConfig, draftComment: string | undefined, chatReport: OnyxEntry<Report>): boolean {
     const {
         betas = [],
@@ -3424,7 +3321,6 @@ export {
     getReportOption,
     getSearchOptions,
     getSearchValueForPhoneOrEmail,
-    getUserToInviteContactOption,
     getUserToInviteOption,
     getValidOptions,
     hasEnabledOptions,
diff --git a/src/libs/PersonalDetailOptionsListUtils/index.ts b/src/libs/PersonalDetailOptionsListUtils/index.ts
index 7a21e7770709..2b6c1a2eca72 100644
--- a/src/libs/PersonalDetailOptionsListUtils/index.ts
+++ b/src/libs/PersonalDetailOptionsListUtils/index.ts
@@ -13,7 +13,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {LoginList, OnyxInputOrEntry, PersonalDetails, PersonalDetailsList, Report, ReportAttributesDerivedValue} from '@src/types/onyx';
 import type {ReportAttributes} from '@src/types/onyx/DerivedValues';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import type {GetOptionsConfig, GetUserToInviteConfig, OptionData, Options, PreviewConfig, PrivateIsArchivedMap} from './types';
+import type {GetContactConfig, GetOptionsConfig, GetUserToInviteConfig, OptionData, Options, PreviewConfig, PrivateIsArchivedMap} from './types';
 
 /**
  * Creates a personal details list option
@@ -121,6 +121,52 @@ function getUserToInviteOption({searchValue, countryCode, formatPhoneNumber, log
     );
 }
 
+function getContactOption({searchValue, firstName, lastName, email, phone, avatar, countryCode, loginList, formatPhoneNumber}: GetContactConfig): OptionData | null {
+    // If email is provided, use it as the primary identifier
+    const effectiveSearchValue = email || searchValue;
+
+    // Handle phone number parsing for either provided phone or searchValue
+    const phoneToCheck = phone || searchValue;
+    const normalizedPhoneNumber = appendCountryCode(Str.removeSMSDomain(phoneToCheck), countryCode);
+    const parsedPhoneNumber = parsePhoneNumber(normalizedPhoneNumber);
+
+    // Validate email (either provided email or searchValue)
+    const isValidEmail = Str.isValidEmail(effectiveSearchValue) && !Str.isDomainEmail(effectiveSearchValue) && !Str.endsWith(effectiveSearchValue, CONST.SMS.DOMAIN);
+
+    const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? ''));
+
+    const sanitizedPhoneLogin = isValidPhoneNumber ? addSMSDomainIfPhoneNumber(parsedPhoneNumber.number?.e164 ?? normalizedPhoneNumber) : '';
+    const login = email ? effectiveSearchValue : (sanitizedPhoneLogin ?? searchValue);
+
+    const isCurrentUserLogin = Object.keys(loginList ?? {}).some((loginToTest) => loginToTest.toLowerCase() === login);
+
+    if (!effectiveSearchValue || isCurrentUserLogin || (!isValidEmail && !isValidPhoneNumber)) {
+        return null;
+    }
+
+    // Generates an optimistic account ID for new users not yet saved in Onyx
+    const optimisticAccountID = generateAccountID(login);
+
+    // Construct display name if firstName/lastName are provided
+
+    const displayName = firstName && lastName ? `${firstName} ${lastName}` : firstName || lastName || effectiveSearchValue;
+
+    // Create the base user details that will be used in both item and participantsList
+    const userDetails = {
+        accountID: optimisticAccountID,
+        avatar: avatar || FallbackAvatar,
+        firstName: firstName ?? '',
+        lastName: lastName ?? '',
+        displayName,
+        login,
+        pronouns: '',
+        phoneNumber: phone ?? '',
+        validated: true,
+    };
+
+    return createOption(userDetails, undefined, formatPhoneNumber);
+}
+
 /**
  * Sort reports by archived status and last visible action
  */
@@ -211,7 +257,8 @@ function getValidOptions(
     countryCode: number,
     loginList?: OnyxEntry<LoginList>,
     {
-        excludeLogins = {},
+        excludeLogins = CONST.EMPTY_OBJECT,
+        excludeFromSuggestionsOnly = CONST.EMPTY_OBJECT,
         includeSelectedOptions = false,
         includeRecentReports = true,
         recentAttendees,
@@ -271,11 +318,16 @@ function getValidOptions(
         }
     }
 
+    const loginsToExcludeFromSuggestions: Record<string, boolean> = {
+        ...loginsToExclude,
+        ...excludeFromSuggestionsOnly,
+    };
+
     // Get valid recent reports:
     let recentOptions: OptionData[] = [];
 
     if (recentAttendees && recentAttendees?.length > 0) {
-        const recentAttendeesSet = new Set(recentAttendees.filter((login) => !loginsToExclude[login]));
+        const recentAttendeesSet = new Set(recentAttendees.filter((login) => !loginsToExcludeFromSuggestions[login]));
         const potentialRecentOptions: Record<string, OptionData> = {};
         for (const option of options) {
             if (!option.login) {
@@ -310,7 +362,7 @@ function getValidOptions(
                 return false;
             }
 
-            if (!!option.login && loginsToExclude[option.login]) {
+            if (!!option.login && loginsToExcludeFromSuggestions[option.login]) {
                 return false;
             }
 
@@ -338,7 +390,7 @@ function getValidOptions(
         ) {
             return false;
         }
-        if (loginsToExclude[personalDetail.login]) {
+        if (loginsToExcludeFromSuggestions[personalDetail.login]) {
             return false;
         }
 
@@ -434,6 +486,6 @@ function getHeaderMessage(translate: LocaleContextProps['translate'], searchValu
     return translate('common.noResultsFound');
 }
 
-export {createOption, canCreateOptimisticPersonalDetailOption, filterOption, matchesSearchTerms, getValidOptions, createOptionList, getHeaderMessage};
+export {createOption, getContactOption, canCreateOptimisticPersonalDetailOption, filterOption, matchesSearchTerms, getValidOptions, createOptionList, getHeaderMessage};
 
 export type {OptionData};
diff --git a/src/libs/PersonalDetailOptionsListUtils/types.ts b/src/libs/PersonalDetailOptionsListUtils/types.ts
index e5eb0db951bf..05959ca9ecf1 100644
--- a/src/libs/PersonalDetailOptionsListUtils/types.ts
+++ b/src/libs/PersonalDetailOptionsListUtils/types.ts
@@ -36,6 +36,7 @@ type PreviewConfig = {
 
 type GetOptionsConfig = {
     excludeLogins?: Record<string, boolean>;
+    excludeFromSuggestionsOnly?: Record<string, boolean>;
     includeCurrentUser?: boolean;
     includeRecentReports?: boolean;
     includeSelectedOptions?: boolean;
@@ -59,6 +60,18 @@ type GetUserToInviteConfig = {
     canInviteUser?: boolean;
 };
 
+type GetContactConfig = {
+    searchValue: string;
+    countryCode: number;
+    formatPhoneNumber: LocaleContextProps['formatPhoneNumber'];
+    loginList: OnyxEntry<LoginList>;
+    firstName: string;
+    lastName: string;
+    email: string;
+    phone: string;
+    avatar: Icon['source'];
+};
+
 type Options = {
     selectedOptions: OptionData[];
     recentOptions: OptionData[];
@@ -68,4 +81,4 @@ type Options = {
 
 type PrivateIsArchivedMap = Record<string, boolean>;
 
-export type {OptionData, GetOptionsConfig, GetUserToInviteConfig, Options, PreviewConfig, PrivateIsArchivedMap};
+export type {OptionData, GetOptionsConfig, GetUserToInviteConfig, GetContactConfig, Options, PreviewConfig, PrivateIsArchivedMap};
diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx
index 3633e9242555..6cf47722969a 100644
--- a/src/pages/RoomInvitePage.tsx
+++ b/src/pages/RoomInvitePage.tsx
@@ -1,5 +1,5 @@
 import {pendingChatMembersSelector} from '@selectors/ReportMetaData';
-import React, {useEffect, useState} from 'react';
+import React, {useEffect} from 'react';
 import type {SectionListData} from 'react-native';
 import {View} from 'react-native';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -13,11 +13,10 @@ import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd
 import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd';
 import useAncestors from '@hooks/useAncestors';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
-import useDebouncedState from '@hooks/useDebouncedState';
 import useDelegateAccountID from '@hooks/useDelegateAccountID';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
-import usePersonalDetailOptions from '@hooks/usePersonalDetailOptions';
+import usePersonalDetailSearchSelector from '@hooks/usePersonalDetailSearchSelector';
 import useReportAttributes from '@hooks/useReportAttributes';
 import useReportIsArchived from '@hooks/useReportIsArchived';
 import useThemeStyles from '@hooks/useThemeStyles';
@@ -27,12 +26,11 @@ import {READ_COMMANDS} from '@libs/API/types';
 import {canUseTouchScreen} from '@libs/DeviceCapabilities';
 import HttpUtils from '@libs/HttpUtils';
 import {appendCountryCode} from '@libs/LoginUtils';
-import memoize from '@libs/memoize';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types';
 import type {OptionData} from '@libs/PersonalDetailOptionsListUtils';
-import {getHeaderMessage, getValidOptions} from '@libs/PersonalDetailOptionsListUtils';
+import {getHeaderMessage} from '@libs/PersonalDetailOptionsListUtils';
 import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils';
 import {addSMSDomainIfPhoneNumber, parsePhoneNumber} from '@libs/PhoneNumber';
 import type {MemberEmailsToAccountIDs} from '@libs/PolicyUtils';
@@ -48,15 +46,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import type {WithReportOrNotFoundProps} from './inbox/report/withReportOrNotFound';
 import withReportOrNotFound from './inbox/report/withReportOrNotFound';
 
-const defaultListOptions = {
-    userToInvite: null,
-    recentOptions: [],
-    personalDetails: [],
-    selectedOptions: [],
-};
-
-const memoizedGetValidOptions = memoize(getValidOptions, {maxSize: 5, monitoringName: 'RoomInvitePage.getValidOptions'});
-
 type RoomInvitePageProps = WithReportOrNotFoundProps & WithNavigationTransitionEndProps & PlatformStackScreenProps<RoomMembersNavigatorParamList, typeof SCREENS.ROOM_MEMBERS.INVITE>;
 
 type MembersSection = SectionListData<OptionData, Section<OptionData>>;
@@ -71,44 +60,14 @@ function RoomInvitePage({
     const styles = useThemeStyles();
     const reportAttributes = useReportAttributes();
     const {translate, formatPhoneNumber} = useLocalize();
-    const {options} = usePersonalDetailOptions({enabled: didScreenTransitionEnd});
-    const areOptionsInitialized = (options?.length ?? 0) > 0;
     const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE);
     const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE);
     const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {selector: pendingChatMembersSelector});
-    const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
     const delegateAccountID = useDelegateAccountID();
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
-    const currentUserEmail = currentUserPersonalDetails.email ?? '';
-    const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? '');
-    const [selectedLogins, setSelectedLogins] = useState<Set<string>>(new Set());
-    const [extraOptions, setExtraOptions] = useState<OptionData[]>([]);
     const [isSearchingForReports] = useOnyx(ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS);
     const isReportArchived = useReportIsArchived(report.reportID);
 
-    const loginToAccountIDMap = (() => {
-        const map: Record<string, number> = {};
-        for (const option of extraOptions) {
-            const login = option.login;
-            if (login) {
-                map[login] = option.accountID;
-            }
-        }
-        for (const option of options ?? []) {
-            const login = option.login;
-            if (login) {
-                map[login] = option.accountID;
-            }
-        }
-        return map;
-    })();
-
-    const transformedOptions =
-        options?.map((option) => ({
-            ...option,
-            isSelected: selectedLogins.has(option.login ?? ''),
-        })) ?? [];
-
     // Any existing participants and Expensify emails should not be eligible for invitation
     const excludedUsers: Record<string, boolean> = {
         ...CONST.EXPENSIFY_EMAILS_OBJECT,
@@ -120,63 +79,42 @@ function RoomInvitePage({
         excludedUsers[smsDomain] = true;
     }
 
-    const optionsList = !areOptionsInitialized
-        ? defaultListOptions
-        : memoizedGetValidOptions(transformedOptions, currentUserEmail, formatPhoneNumber, countryCode, loginList, {
-              excludeLogins: excludedUsers,
-              extraOptions,
-              includeRecentReports: false,
-              searchString: debouncedSearchTerm,
-              includeCurrentUser: false,
-              includeUserToInvite: true,
-          });
+    const {searchTerm, debouncedSearchTerm, setSearchTerm, selectedOptions, availableOptions, toggleSelection, areOptionsInitialized} = usePersonalDetailSearchSelector({
+        shouldInitialize: didScreenTransitionEnd,
+        selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI,
+        excludeLogins: excludedUsers,
+        includeCurrentUser: false,
+        includeRecentReports: false,
+        includeUserToInvite: true,
+        initialSearchPhrase: userSearchPhrase,
+    });
 
     const sections: MembersSection[] = [];
     if (areOptionsInitialized) {
-        if (optionsList.userToInvite) {
+        if (availableOptions.userToInvite) {
             sections.push({
                 title: undefined,
-                data: [optionsList.userToInvite],
+                data: [availableOptions.userToInvite],
                 sectionIndex: 0,
             });
         } else {
-            if (optionsList.selectedOptions.length > 0) {
+            if (availableOptions.selectedOptions.length > 0) {
                 sections.push({
                     title: undefined,
-                    data: optionsList.selectedOptions,
+                    data: availableOptions.selectedOptions,
                     sectionIndex: 0,
                 });
             }
-            if (optionsList.personalDetails.length > 0) {
+            if (availableOptions.personalDetails.length > 0) {
                 sections.push({
                     title: translate('common.contacts'),
-                    data: optionsList.personalDetails,
-                    sectionIndex: optionsList.selectedOptions.length > 0 ? 1 : 0,
+                    data: availableOptions.personalDetails,
+                    sectionIndex: availableOptions.selectedOptions.length > 0 ? 1 : 0,
                 });
             }
         }
     }
 
-    const existingLogins = new Set(options?.map((option) => option.login ?? ''));
-
-    const toggleOption = (option: OptionData) => {
-        const isSelected = selectedLogins.has(option.login ?? '');
-
-        if (isSelected) {
-            // If the option is selected, remove it from the selected logins
-            const isInExtraOption = extraOptions.some((extraOption) => extraOption.login === option.login);
-            if (isInExtraOption) {
-                setExtraOptions((prev) => prev.filter((extraOption) => extraOption.login !== option.login));
-            }
-            setSelectedLogins((prev) => new Set([...prev].filter((login) => login !== option.login)));
-        } else {
-            setSelectedLogins((prev) => new Set([...prev, option.login ?? '']));
-            if (!existingLogins.has(option.login ?? '')) {
-                setExtraOptions((prev) => [...prev, {...option, isSelected: true}]);
-            }
-        }
-    };
-
     // Non policy members should not be able to view the participants of a room
     const reportID = report?.reportID;
     const isPolicyEmployee = isPolicyEmployeeUtil(report?.policyID, policy);
@@ -186,17 +124,18 @@ function RoomInvitePage({
 
     const ancestors = useAncestors(report);
 
-    const validSelectedLogins = Array.from(selectedLogins).filter((login) => !excludedUsers[login]);
+    const validSelectedOptions = selectedOptions.filter((option) => !excludedUsers[option.login ?? '']);
 
     const inviteUsers = () => {
         HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_USERS);
 
-        if (validSelectedLogins.length === 0) {
+        if (validSelectedOptions.length === 0) {
             return;
         }
         const invitedEmailsToAccountIDs: MemberEmailsToAccountIDs = {};
-        for (const login of validSelectedLogins) {
-            const accountID = loginToAccountIDMap[login] ?? CONST.DEFAULT_NUMBER_ID;
+        for (const option of validSelectedOptions) {
+            const login = option.login ?? '';
+            const accountID = option.accountID;
             invitedEmailsToAccountIDs[login] = accountID;
         }
         if (report?.reportID) {
@@ -272,7 +211,7 @@ function RoomInvitePage({
                     sections={sections}
                     ListItem={InviteMemberListItem}
                     textInputOptions={textInputOptions}
-                    onSelectRow={toggleOption}
+                    onSelectRow={toggleSelection}
                     confirmButtonOptions={{
                         onConfirm: inviteUsers,
                     }}
@@ -284,7 +223,7 @@ function RoomInvitePage({
                 />
                 <View style={[styles.flexShrink0]}>
                     <FormAlertWithSubmitButton
-                        isDisabled={!validSelectedLogins.length}
+                        isDisabled={!validSelectedOptions.length}
                         buttonText={translate('common.invite')}
                         onSubmit={inviteUsers}
                         containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5, styles.ph5]}
diff --git a/tests/unit/useSearchSelectorTest.tsx b/tests/unit/useSearchSelectorTest.tsx
index 31f85775a7f0..9ca977a6d319 100644
--- a/tests/unit/useSearchSelectorTest.tsx
+++ b/tests/unit/useSearchSelectorTest.tsx
@@ -1,7 +1,7 @@
 import {act, renderHook} from '@testing-library/react-native';
 import type {OnyxMultiSetInput} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
-import useSearchSelectorBase from '@hooks/useSearchSelector.base';
+import useSearchSelectorBase from '@hooks/useSearchSelector/base';
 import {getSearchOptions, getValidOptions} from '@libs/OptionsListUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
