import Fuse, { type IFuseOptions } from "fuse.js" import type { FullContact } from "./types" function stripAccents(str: string): string { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "") } const fuseOptions: IFuseOptions = { keys: [ "firstName", "lastName", "middleName", "nicknames", "emails.value", "phones.value", "company", "department", "jobTitle", "addresses.street", "addresses.city", "addresses.country", ], isCaseSensitive: false, threshold: 0.4, ignoreLocation: true, getFn: (obj: FullContact, path: string | string[]) => { const raw = Fuse.config.getFn(obj, path) if (Array.isArray(raw)) { return raw.map((v) => (typeof v === "string" ? stripAccents(v) : v)) as unknown as string } if (typeof raw === "string") { return stripAccents(raw) } return raw as unknown as string }, } let cachedFuse: { key: FullContact[]; fuse: Fuse } | null = null function getFuse(contacts: FullContact[]): Fuse { if (cachedFuse && cachedFuse.key === contacts) return cachedFuse.fuse const fuse = new Fuse(contacts, fuseOptions) cachedFuse = { key: contacts, fuse } return fuse } export function searchContacts( contacts: FullContact[], query: string ): FullContact[] { if (!query.trim()) return contacts const fuse = getFuse(contacts) const results = fuse.search(stripAccents(query)) const seen = new Set() const deduped: FullContact[] = [] for (const r of results) { if (!seen.has(r.item.id)) { seen.add(r.item.id) deduped.push(r.item) } } return deduped }