import { DeviceType } from 'expo-device'; import * as Device from 'expo-device'; import { format } from 'date-fns'; import { useQuery } from "@tanstack/react-query"; interface EventTimestamp { seconds: number; nanoseconds: number; } interface Event { id: string; title: string; start: Date | EventTimestamp; startDate?: EventTimestamp; end: Date | EventTimestamp; endDate?: EventTimestamp; allDay: boolean; eventColor: string; attendees?: string[]; creatorId?: string; } interface FormattedEvent { id: string; title: string; start: { date: string } | { dateTime: string; timeZone: string }; end: { date: string } | { dateTime: string; timeZone: string }; color: string; } const createEventHash = (event: FormattedEvent): string => { const startTime = 'date' in event.start ? event.start.date : event.start.dateTime; const endTime = 'date' in event.end ? event.end.date : event.end.dateTime; const str = `${startTime}-${endTime}-${event.title}`; let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return hash.toString(36); }; // Precompute time constants const DAY_IN_MS = 24 * 60 * 60 * 1000; const PERIOD_IN_MS = 5 * DAY_IN_MS; const TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone; // Memoize date range calculations const getDateRangeKey = (timestamp: number) => Math.floor(timestamp / PERIOD_IN_MS); // Optimized date validation and conversion const getValidDate = (date: any, timestamp?: EventTimestamp): Date | null => { try { if (timestamp?.seconds) { return new Date(timestamp.seconds * 1000); } if (date instanceof Date) { return date; } if (typeof date === 'string') { return new Date(date); } return null; } catch { return null; } }; // Batch process events const processEvents = async ( events: Event[], selectedDate: Date, selectedUser: { uid: string } | null ): Promise => { if (!events.length) return []; const currentRangeKey = getDateRangeKey(selectedDate.getTime()); const isTablet = Device.deviceType === DeviceType.TABLET; const userId = selectedUser?.uid; const uniqueEvents = new Map(); const processedHashes = new Set(); const CHUNK_SIZE = 100; for (let i = 0; i < events.length; i += CHUNK_SIZE) { const chunk = events.slice(i, i + CHUNK_SIZE); await new Promise(resolve => setTimeout(resolve, 0)); for (const event of chunk) { try { if (isTablet && userId && !event.attendees?.includes(userId) && event.creatorId !== userId) { continue; } const startDate = getValidDate(event.start, event.startDate); if (!startDate) continue; const rangeKey = getDateRangeKey(startDate.getTime()); if (Math.abs(rangeKey - currentRangeKey) > 1) continue; const endDate = getValidDate(event.end, event.endDate); if (!endDate) continue; const formattedEvent = event.allDay ? { id: event.id, title: event.title, start: { date: format(startDate, 'yyyy-MM-dd') }, end: { date: format(endDate, 'yyyy-MM-dd') }, color: event.eventColor } : { id: event.id, title: event.title, start: { dateTime: startDate.toISOString(), timeZone: TIME_ZONE }, end: { dateTime: endDate.toISOString(), timeZone: TIME_ZONE }, color: event.eventColor }; const hash = createEventHash(formattedEvent); if (!processedHashes.has(hash)) { processedHashes.add(hash); uniqueEvents.set(event.id, formattedEvent); } } catch (error) { console.error('Error processing event:', event.id, error); continue; } } } return Array.from(uniqueEvents.values()); }; export const useFormattedEvents = ( events: Event[], selectedDate: Date, selectedUser: { uid: string } | null ) => { return useQuery({ queryKey: ['formattedEvents', events, selectedDate, selectedUser?.uid], queryFn: () => processEvents(events, selectedDate, selectedUser), enabled: events.length > 0, staleTime: Infinity, placeholderData: (previousData) => previousData, gcTime: Infinity }); };