import { buildWebGroupRidePayload, type WebGroupRideInput } from './group-ride-payload'; export type CreateRideResult = | { ok: true; rideId: string; payload: Record } | { ok: false; reason: 'unauthenticated' } | { ok: false; reason: 'unavailable' } | { ok: false; reason: 'commit-failed'; error: unknown }; export interface FirestoreBatchLike { // Loose signatures so the real firebase WriteBatch is assignable here (its set() is // generic over DocumentReference and returns WriteBatch). Tests pass a simple fake. // eslint-disable-next-line @typescript-eslint/no-explicit-any set: (ref: any, data: any) => unknown; commit: () => Promise; } export interface CommitGroupRideDeps { uid?: string | null; firestore: unknown | null; leadDisplayName: string; leadHandle: string; /** Injected Firestore primitives so this is unit-testable without the firebase SDK. */ writeBatch: (firestore: any) => FirestoreBatchLike; docRef: (firestore: any, ...segments: string[]) => unknown; now?: Date; } /** * Persists a web-created group ride and reports the outcome. It performs the backend write * and resolves ONLY after the batch commit succeeds, so callers can insert into UI state * strictly after confirmation (no phantom rides). It never fabricates coordinates (see * buildWebGroupRidePayload) and the returned rideId is the real Firestore document id used * as the join code. * * Returns a discriminated result instead of throwing, so every failure path is explicit: * - unauthenticated: no uid (an unauthenticated user cannot create a persisted ride) * - unavailable: no Firestore handle * - commit-failed: the write was attempted but rejected (nothing persisted) */ let rideIdCounter = 0; export function resetRideIdCounter(): void { rideIdCounter = 0; } export async function commitGroupRide( newRide: WebGroupRideInput, deps: CommitGroupRideDeps ): Promise { const uid = deps.uid; if (!uid) return { ok: false, reason: 'unauthenticated' }; if (!deps.firestore) return { ok: false, reason: 'unavailable' }; const now = deps.now ?? new Date(); const seq = ++rideIdCounter; const seqSuffix = seq > 1 ? `${seq}` : ''; const rideId = `ride_${now.getTime()}${seqSuffix}_${uid.substring(0, Math.min(6, uid.length))}`; const payload = buildWebGroupRidePayload(newRide, { rideId, uid, leadHandle: deps.leadHandle, leadDisplayName: deps.leadDisplayName, now, }); const organizerParticipant = { userId: uid, role: 'organizer', status: 'accepted', riderHandle: deps.leadHandle, riderDisplayName: deps.leadDisplayName, createdAt: now.toISOString(), updatedAt: now.toISOString(), }; try { const batch = deps.writeBatch(deps.firestore); batch.set(deps.docRef(deps.firestore, 'group_rides', rideId), payload); batch.set(deps.docRef(deps.firestore, 'group_rides', rideId, 'participants', uid), organizerParticipant); await batch.commit(); return { ok: true, rideId, payload }; } catch (error) { return { ok: false, reason: 'commit-failed', error }; } }