import { create } from 'zustand'; import { nanoid } from 'nanoid'; import type { Room, Participant, ParticipantLocation, ParticipantStatus, Waypoint, RoomSettings, PrecisionLevel, Route, RouteSegment, } from '@/types'; // Route state for navigation interface ActiveRoute { id: string; from: { type: 'participant' | 'waypoint' | 'current'; id?: string; name: string; }; to: { type: 'participant' | 'waypoint'; id: string; name: string; }; segments: RouteSegment[]; totalDistance: number; estimatedTime: number; summary: string; isLoading: boolean; error?: string; } // Color palette for participants const COLORS = [ '#10b981', // emerald '#6366f1', // indigo '#f59e0b', // amber '#ef4444', // red '#8b5cf6', // violet '#ec4899', // pink '#14b8a6', // teal '#f97316', // orange '#84cc16', // lime '#06b6d4', // cyan ]; interface RoomState { room: Room | null; participants: Participant[]; currentParticipantId: string | null; isConnected: boolean; error: string | null; activeRoute: ActiveRoute | null; // Actions joinRoom: (slug: string, name: string, emoji: string) => void; leaveRoom: () => void; updateParticipant: (updates: Partial) => void; updateLocation: (location: ParticipantLocation) => void; setStatus: (status: ParticipantStatus) => void; addWaypoint: (waypoint: Omit) => void; removeWaypoint: (waypointId: string) => void; // Route actions navigateTo: (target: { type: 'participant' | 'waypoint'; id: string }) => Promise; clearRoute: () => void; // Internal _syncFromDocument: (doc: unknown) => void; } export const useRoomStore = create((set, get) => ({ room: null, participants: [], currentParticipantId: null, isConnected: false, error: null, activeRoute: null, joinRoom: (slug: string, name: string, emoji: string) => { const participantId = nanoid(); const colorIndex = Math.floor(Math.random() * COLORS.length); const participant: Participant = { id: participantId, name, emoji, color: COLORS[colorIndex], joinedAt: new Date(), lastSeen: new Date(), status: 'online', privacySettings: { sharingEnabled: true, defaultPrecision: 'exact' as PrecisionLevel, showIndoorFloor: true, ghostMode: false, }, }; // Create or join room const room: Room = { id: nanoid(), slug, name: slug, createdAt: new Date(), createdBy: participantId, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days settings: { maxParticipants: 10, defaultPrecision: 'exact' as PrecisionLevel, allowGuestJoin: true, showC3NavIndoor: true, }, participants: new Map([[participantId, participant]]), waypoints: [], }; set({ room, participants: [participant], currentParticipantId: participantId, isConnected: true, error: null, }); // TODO: Connect to Automerge sync server console.log(`Joined room: ${slug} as ${name} (${emoji})`); }, leaveRoom: () => { const { room, currentParticipantId } = get(); if (room && currentParticipantId) { room.participants.delete(currentParticipantId); } set({ room: null, participants: [], currentParticipantId: null, isConnected: false, }); }, updateParticipant: (updates: Partial) => { const { room, currentParticipantId, participants } = get(); if (!room || !currentParticipantId) return; const current = room.participants.get(currentParticipantId); if (!current) return; const updated = { ...current, ...updates, lastSeen: new Date() }; room.participants.set(currentParticipantId, updated); set({ participants: participants.map((p) => p.id === currentParticipantId ? updated : p ), }); }, updateLocation: (location: ParticipantLocation) => { get().updateParticipant({ location }); }, setStatus: (status: ParticipantStatus) => { get().updateParticipant({ status }); }, addWaypoint: (waypoint) => { const { room, currentParticipantId } = get(); if (!room || !currentParticipantId) return; const newWaypoint: Waypoint = { ...waypoint, id: nanoid(), createdAt: new Date(), createdBy: currentParticipantId, }; room.waypoints.push(newWaypoint); set({ room: { ...room } }); }, removeWaypoint: (waypointId: string) => { const { room } = get(); if (!room) return; room.waypoints = room.waypoints.filter((w) => w.id !== waypointId); set({ room: { ...room } }); }, navigateTo: async (target: { type: 'participant' | 'waypoint'; id: string }) => { const { participants, room, currentParticipantId } = get(); // Get current user's location const currentUser = participants.find((p) => p.id === currentParticipantId); if (!currentUser?.location) { set({ activeRoute: { id: nanoid(), from: { type: 'current', name: 'You' }, to: { type: target.type, id: target.id, name: 'Target' }, segments: [], totalDistance: 0, estimatedTime: 0, summary: '', isLoading: false, error: 'Enable location sharing to get directions', }, }); return; } // Get destination let destLocation: { latitude: number; longitude: number; indoor?: { level: number; x: number; y: number } } | null = null; let destName = ''; if (target.type === 'participant') { const participant = participants.find((p) => p.id === target.id); if (participant?.location) { destLocation = { latitude: participant.location.latitude, longitude: participant.location.longitude, indoor: participant.location.indoor, }; destName = participant.name; } } else if (target.type === 'waypoint') { const waypoint = room?.waypoints.find((w) => w.id === target.id); if (waypoint) { destLocation = { latitude: waypoint.location.latitude, longitude: waypoint.location.longitude, indoor: waypoint.location.indoor, }; destName = waypoint.name; } } if (!destLocation) { set({ activeRoute: { id: nanoid(), from: { type: 'current', name: currentUser.name }, to: { type: target.type, id: target.id, name: destName || 'Unknown' }, segments: [], totalDistance: 0, estimatedTime: 0, summary: '', isLoading: false, error: 'Destination location not available', }, }); return; } // Set loading state set({ activeRoute: { id: nanoid(), from: { type: 'current', name: currentUser.name }, to: { type: target.type, id: target.id, name: destName }, segments: [], totalDistance: 0, estimatedTime: 0, summary: '', isLoading: true, }, }); try { const response = await fetch('/api/routing', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ origin: { latitude: currentUser.location.latitude, longitude: currentUser.location.longitude, indoor: currentUser.location.indoor, }, destination: destLocation, mode: 'walking', eventId: room?.settings.eventId || '39c3', }), }); const data = await response.json(); if (data.success && data.route) { set({ activeRoute: { id: nanoid(), from: { type: 'current', name: currentUser.name }, to: { type: target.type, id: target.id, name: destName }, segments: data.route.segments, totalDistance: data.route.totalDistance, estimatedTime: data.route.estimatedTime, summary: data.route.summary, isLoading: false, }, }); } else { set({ activeRoute: { id: nanoid(), from: { type: 'current', name: currentUser.name }, to: { type: target.type, id: target.id, name: destName }, segments: [], totalDistance: 0, estimatedTime: 0, summary: '', isLoading: false, error: data.error || 'Could not calculate route', }, }); } } catch (error) { console.error('Navigation error:', error); set({ activeRoute: { id: nanoid(), from: { type: 'current', name: currentUser.name }, to: { type: target.type, id: target.id, name: destName }, segments: [], totalDistance: 0, estimatedTime: 0, summary: '', isLoading: false, error: 'Failed to calculate route', }, }); } }, clearRoute: () => { set({ activeRoute: null }); }, _syncFromDocument: (doc: unknown) => { // TODO: Implement Automerge document sync console.log('Sync from document:', doc); }, }));