/** * Sort/geo helpers shared across pages. Kept out of SortDropdown.tsx so that * component file exports only a component (React Fast Refresh). */ import ngeohash from 'ngeohash'; interface LatLng { lat: number; lng: number; } /** * Distance in km from `from` to a listing's own geohash, or null when the * listing carries no location. Used for the marketplace "Near me" sort + badges. */ export function itemDistanceKm(item: { geohash?: string }, from: LatLng): number | null { if (!item.geohash) return null; try { const { latitude, longitude } = ngeohash.decode(item.geohash); if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null; return calculateDistance(from.lat, from.lng, latitude, longitude); } catch { return null; } } /** Weighted popularity score: comments 3x, reactions 1x, zaps 5x. */ export function calculatePopularity( commentCount = 0, reactionCount = 0, zapCount = 0, ): number { return commentCount * 3 + reactionCount * 1 + zapCount * 5; } /** Great-circle distance between two coordinates, in kilometres (Haversine). */ export function calculateDistance( lat1: number, lon1: number, lat2: number, lon2: number, ): number { const R = 6371; // Earth's radius in km const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function toRad(degrees: number): number { return degrees * (Math.PI / 180); }