import { useEffect, useState, useRef, useCallback } from 'react'
import { X, Navigation, Plus, Bookmark, ChevronDown, Copy } from 'lucide-react'
import toast from 'react-hot-toast'
import { useStore } from '../store'
import { fetchElevation } from '../api'
/** Meters to feet */
const M_TO_FT = 3.28084
/** Build display address from raw result data */
function buildAddress(place) {
if (place.address) return place.address
const raw = place.raw || {}
const parts = [raw.street, raw.city, raw.state, raw.postcode].filter(Boolean)
return parts.join(', ') || null
}
/** Copy popover — small dropdown beneath the Copy button */
function CopyPopover({ address, selectedPlace, onClose }) {
const ref = useRef(null)
// Close on click-outside
useEffect(() => {
function handleClick(e) {
if (ref.current && !ref.current.contains(e.target)) onClose()
}
function handleKey(e) {
if (e.key === 'Escape') onClose()
}
document.addEventListener('mousedown', handleClick)
document.addEventListener('keydown', handleKey)
return () => {
document.removeEventListener('mousedown', handleClick)
document.removeEventListener('keydown', handleKey)
}
}, [onClose])
const copyAddress = () => {
const text = [selectedPlace.name, address].filter(Boolean).join('\n')
navigator.clipboard.writeText(text).then(
() => toast('Address copied'),
() => toast.error('Failed to copy')
)
onClose()
}
const copyCoords = () => {
const text = `${selectedPlace.lat.toFixed(6)}, ${selectedPlace.lon.toFixed(6)}`
navigator.clipboard.writeText(text).then(
() => toast('Coordinates copied'),
() => toast.error('Failed to copy')
)
onClose()
}
return (
)
}
export default function PlaceDetail() {
const selectedPlace = useStore((s) => s.selectedPlace)
const clearSelectedPlace = useStore((s) => s.clearSelectedPlace)
const startDirections = useStore((s) => s.startDirections)
const addStop = useStore((s) => s.addStop)
const stops = useStore((s) => s.stops)
const geoPermission = useStore((s) => s.geoPermission)
const [elevResult, setElevResult] = useState({ lat: null, lon: null, value: null })
const [isMobile, setIsMobile] = useState(false)
const [copyForPlace, setCopyForPlace] = useState(null)
const closeCopy = useCallback(() => setCopyForPlace(null), [])
useEffect(() => {
const check = () => setIsMobile(window.innerWidth < 768)
check()
window.addEventListener('resize', check)
return () => window.removeEventListener('resize', check)
}, [])
// Escape key closes panel
useEffect(() => {
if (!selectedPlace) return
function handleKey(e) {
if (e.key === 'Escape') clearSelectedPlace()
}
document.addEventListener('keydown', handleKey)
return () => document.removeEventListener('keydown', handleKey)
}, [selectedPlace, clearSelectedPlace])
// Fetch elevation when place changes
const placeLat = selectedPlace?.lat
const placeLon = selectedPlace?.lon
useEffect(() => {
if (placeLat == null || placeLon == null) return
let cancelled = false
fetchElevation(placeLat, placeLon).then((h) => {
if (!cancelled) setElevResult({ lat: placeLat, lon: placeLon, value: h })
})
return () => { cancelled = true }
}, [placeLat, placeLon])
// Derive elevation/loading from comparing result to current place
const elevLoading = placeLat != null && (elevResult.lat !== placeLat || elevResult.lon !== placeLon)
const elevation = !elevLoading ? elevResult.value : null
const placeKey = selectedPlace ? `${selectedPlace.lat},${selectedPlace.lon}` : null
if (!selectedPlace) return null
const address = buildAddress(selectedPlace)
const elevFeet = elevation != null ? Math.round(elevation * M_TO_FT) : null
const raw = selectedPlace.raw || {}
// Check if place is already in stops
const existingStopIndex = stops.findIndex(
(s) => Math.abs(s.lat - selectedPlace.lat) < 0.00001 && Math.abs(s.lon - selectedPlace.lon) < 0.00001
)
const handleDirections = () => {
startDirections(selectedPlace)
if (geoPermission !== 'granted' && stops.length === 0) {
toast('Set a starting point to get directions', { icon: '\u{1F4CD}' })
}
}
const handleAddStop = () => {
addStop({
lat: selectedPlace.lat,
lon: selectedPlace.lon,
name: selectedPlace.name,
source: selectedPlace.source,
matchCode: selectedPlace.matchCode,
})
clearSelectedPlace()
}
const handleSave = () => {
toast('Saved places coming soon')
}
const panelContent = (
<>
{/* Close button */}
{/* Place name */}
{selectedPlace.name}
{selectedPlace.type && (
{selectedPlace.type}
)}
{/* Address */}
{address && (
{address}
)}
{/* Coordinates + elevation */}
{selectedPlace.lat.toFixed(6)}, {selectedPlace.lon.toFixed(6)}
·
{elevLoading ? '...' : elevFeet != null ? `${elevFeet.toLocaleString()} ft` : '\u2014'}
{/* Optional extras */}
{(raw.opening_hours || raw.website || raw.phone) && (
)}
{/* Action buttons */}
{existingStopIndex >= 0 ? (
Added as stop {String.fromCharCode(65 + existingStopIndex)}
) : (
)}
{/* Copy dropdown */}
{copyForPlace === placeKey && (
)}
>
)
// Mobile: bottom overlay
if (isMobile) {
return (
{panelContent}
)
}
// Desktop: side panel
return (
{panelContent}
)
}