import React, { useState, useEffect, useRef } from 'react'; import { Button } from '../components/ui/Button'; import { Map as MapIcon, Loader2, Navigation, Compass, Search, List, X, MapPin, History, BookOpen, Star, MessageSquare, ArrowLeft, LocateFixed, Mountain, Landmark, Droplets, User, Info, Layout, Layers, Bot, Route, ChevronUp, ChevronDown, Satellite } from 'lucide-react'; import { HeritageService } from '../services/heritageService'; import { StorageService } from '../services/storageService'; import { HeritageSite, Review, ProvinceData } from '../types'; import { PROVINCES } from '../data/staticData'; import { useLanguage } from '../contexts/LanguageContext'; declare const L: any; // Leaflet global // Satellite Map Tile URL (Esri World Imagery) const SATELLITE_TILE_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'; const SATELLITE_ATTRIBUTION = 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'; const HeritageMap: React.FC = () => { const { t, language } = useLanguage(); const [sites, setSites] = useState([]); const [filteredSites, setFilteredSites] = useState([]); const [filteredProvinces, setFilteredProvinces] = useState(PROVINCES); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState('All'); // Selection State const [selectedSite, setSelectedSite] = useState(null); const [selectedProvince, setSelectedProvince] = useState(null); const [loading, setLoading] = useState(true); // Location const [userPos, setUserPos] = useState<[number, number] | null>(null); // Mode State const [activeMode, setActiveMode] = useState<'heritage' | 'provinces'>('heritage'); const [isMobileListExpanded, setIsMobileListExpanded] = useState(false); const mapContainerRef = useRef(null); const mapInstanceRef = useRef(null); const markerClusterGroupRef = useRef(null); const provinceLayerRef = useRef(null); const userMarkerRef = useRef(null); const tileLayerRef = useRef(null); // --- LIVE LOCATION TRACKING --- useEffect(() => { if (navigator.geolocation) { const watchId = navigator.geolocation.watchPosition( (position) => { const { latitude, longitude } = position.coords; setUserPos([latitude, longitude]); if (mapInstanceRef.current) { if (userMarkerRef.current) { userMarkerRef.current.setLatLng([latitude, longitude]); } else { const icon = L.divIcon({ className: 'user-location-marker', html: `
`, iconSize: [16, 16], iconAnchor: [8, 8] }); userMarkerRef.current = L.marker([latitude, longitude], { icon, zIndexOffset: 1000 }).addTo(mapInstanceRef.current); } } }, (error) => console.warn("Location error:", error), { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 } ); return () => navigator.geolocation.clearWatch(watchId); } }, []); // --- RUDRA CONTROL --- useEffect(() => { const handleRemoteControl = (e: any) => { const { type, targetName } = e.detail; if (type === 'province') { const province = PROVINCES.find(p => p.name.toLowerCase().includes(targetName.toLowerCase())); if (province) { handleSelectProvince(province); // Force open info tab if handled by AI setSelectedProvince(province); } } else if (type === 'site') { const site = sites.find(s => s.name.toLowerCase().includes(targetName.toLowerCase())); if (site) { handleSelectSite(site); setSelectedSite(site); } } else if (type === 'reset') { mapInstanceRef.current?.flyTo([28.3949, 84.1240], 7); setSelectedSite(null); setSelectedProvince(null); } }; window.addEventListener('rudraksha-map-control', handleRemoteControl); return () => window.removeEventListener('rudraksha-map-control', handleRemoteControl); }, [sites]); useEffect(() => { const loadSites = async () => { setLoading(true); const data = await HeritageService.getAllSites(); setSites(data); setFilteredSites(data); setLoading(false); setTimeout(() => initMap(data), 100); }; loadSites(); }, []); // Filtering Logic useEffect(() => { const query = searchQuery.toLowerCase(); if (activeMode === 'heritage') { let result = sites; if (query) { result = result.filter(site => (language === 'ne' ? site.nameNe : site.name).toLowerCase().includes(query)); } if (selectedCategory !== 'All') { result = result.filter(site => site.category === selectedCategory); } setFilteredSites(result); if (mapInstanceRef.current) updateHeritageMarkers(result); // Hide province markers if (provinceLayerRef.current) provinceLayerRef.current.clearLayers(); } else { // Province Mode let result = PROVINCES; if (query) { result = result.filter(prov => (language === 'ne' ? prov.nepaliName : prov.name).toLowerCase().includes(query)); } setFilteredProvinces(result); if (mapInstanceRef.current) updateProvinceMarkers(result); // Hide heritage markers if (markerClusterGroupRef.current) markerClusterGroupRef.current.clearLayers(); } }, [searchQuery, selectedCategory, sites, activeMode, language]); const initMap = (initialSites: HeritageSite[]) => { if (!mapContainerRef.current || mapInstanceRef.current || typeof L === 'undefined') return; const map = L.map(mapContainerRef.current, { zoomControl: false }).setView([28.3949, 84.1240], 7); mapInstanceRef.current = map; L.control.zoom({ position: 'bottomright' }).addTo(map); // Set Satellite Tile Layer tileLayerRef.current = L.tileLayer(SATELLITE_TILE_URL, { maxZoom: 19, attribution: SATELLITE_ATTRIBUTION }).addTo(map); // Add Layer Groups if (L.markerClusterGroup) { markerClusterGroupRef.current = L.markerClusterGroup({ showCoverageOnHover: false, zoomToBoundsOnClick: true, removeOutsideVisibleBounds: true }); map.addLayer(markerClusterGroupRef.current); } else { markerClusterGroupRef.current = L.layerGroup().addTo(map); } provinceLayerRef.current = L.layerGroup().addTo(map); // Initial Load if (activeMode === 'heritage') updateHeritageMarkers(initialSites); else updateProvinceMarkers(PROVINCES); }; const handleSelectSite = (site: HeritageSite) => { setSelectedProvince(null); setSelectedSite(site); setActiveMode('heritage'); setIsMobileListExpanded(false); mapInstanceRef.current?.flyTo([site.latitude, site.longitude], 16, { duration: 1.5 }); }; const handleSelectProvince = (prov: ProvinceData) => { setSelectedSite(null); setSelectedProvince(prov); setActiveMode('provinces'); setIsMobileListExpanded(false); mapInstanceRef.current?.flyTo([prov.lat, prov.lng], 9, { duration: 1.5 }); }; const updateHeritageMarkers = (sitesData: HeritageSite[]) => { if (!markerClusterGroupRef.current) return; markerClusterGroupRef.current.clearLayers(); const newMarkers: any[] = []; sitesData.forEach(site => { const icon = L.divIcon({ className: 'custom-icon', html: `
`, iconSize: [48, 48], iconAnchor: [24, 24] }); const marker = L.marker([site.latitude, site.longitude], { icon }); marker.on('click', () => handleSelectSite(site)); newMarkers.push(marker); }); if (markerClusterGroupRef.current.addLayers) { markerClusterGroupRef.current.addLayers(newMarkers); } else { newMarkers.forEach(m => markerClusterGroupRef.current.addLayer(m)); } }; const updateProvinceMarkers = (provincesData: ProvinceData[]) => { if (!provinceLayerRef.current) return; provinceLayerRef.current.clearLayers(); provincesData.forEach(prov => { // Same style as heritage markers now const icon = L.divIcon({ html: `
${prov.name}
`, className: 'bg-transparent', iconSize: [48, 48], iconAnchor: [24, 24] }); const marker = L.marker([prov.lat, prov.lng], { icon }); marker.on('click', () => handleSelectProvince(prov)); marker.addTo(provinceLayerRef.current); }); }; const categories = ['All', 'Temple', 'Stupa', 'Palace', 'Nature', 'Other']; const localizeNumber = (value: string | number): string => { if (language === 'en') return value.toString(); const str = value.toString(); const digits = ['०', '१', '२', '३', '४', '५', '६', '७', '८', '९']; return str.replace(/[0-9]/g, (m) => digits[parseInt(m)]); }; return (
{/* MAP CONTAINER */}
{/* LOADING OVERLAY */} {loading && (
)} {/* TOP CONTROLS (Floating Mode Toggle) */}
{/* SEARCH & LIST PANEL (Responsive: Sidebar on Desktop, Bottom Sheet on Mobile) */}
{/* Search Box */}
{/* Mobile Toggle Handle */}
setIsMobileListExpanded(!isMobileListExpanded)}>
{ setSearchQuery(e.target.value); setIsMobileListExpanded(true); }} onFocus={() => setIsMobileListExpanded(true)} placeholder={activeMode === 'heritage' ? t("Search heritage...", "Search heritage...") : t("Search provinces...", "Search provinces...")} className="w-full pl-10 pr-4 py-3 bg-gray-100 dark:bg-gray-800 rounded-xl text-sm font-bold outline-none focus:ring-2 ring-red-500/50 dark:text-white transition-all" />
{activeMode === 'heritage' && (
{categories.map(cat => ( ))}
)}
{/* Results List */}
{activeMode === 'heritage' ? ( filteredSites.map(site => (
handleSelectSite(site)} className={`flex items-center gap-3 p-2 rounded-xl cursor-pointer transition-all border-2 ${selectedSite?.id === site.id ? 'bg-red-50 dark:bg-red-900/20 border-red-500' : 'bg-transparent border-transparent hover:bg-gray-100 dark:hover:bg-gray-800'}`}>

{language === 'ne' ? (site.nameNe || site.name) : site.name}

{site.category} • {site.region}

)) ) : ( filteredProvinces.map(prov => (
handleSelectProvince(prov)} className={`flex items-center gap-3 p-2 rounded-xl cursor-pointer transition-all border-2 ${selectedProvince?.id === prov.id ? 'bg-blue-50 dark:bg-blue-900/20 border-blue-500' : 'bg-transparent border-transparent hover:bg-gray-100 dark:hover:bg-gray-800'}`}>

{language === 'ne' ? prov.nepaliName : prov.name}

{localizeNumber(prov.districts)} Districts

)) )}
{/* DETAILS PANEL (Unified for both Modes) */} {(selectedSite || selectedProvince) && (
{/* Panel Header Image */}
Header
{selectedSite ? selectedSite.category : `Province ${localizeNumber(selectedProvince!.id)}`}

{selectedSite ? (language === 'ne' ? selectedSite.nameNe : selectedSite.name) : (language === 'ne' ? selectedProvince?.nepaliName : selectedProvince?.name)}

{/* Content Body */}
{/* Province Specific Stats Grid */} {selectedProvince ? ( <>

Capital

{language === 'ne' ? selectedProvince.capitalNe : selectedProvince.capital}

Area

{localizeNumber(selectedProvince.area)}

{/* Capital Satellite Uplink (Static Visual Placeholder) */}
Capital Uplink
{/* Using a generic high-res satellite looking image for visual effect since real-time requires key */} Satellite View
) : (

{selectedSite?.region}

)}

About

{selectedSite ? (language === 'ne' ? selectedSite.descriptionNe : selectedSite.description) : (language === 'ne' ? selectedProvince?.descriptionNe : selectedProvince?.description)}

{selectedSite ? ( ) : (

Attractions

    {(language === 'en' ? selectedProvince!.attractions : selectedProvince!.attractionsNe).map((attr, idx) => (
  • {attr}
  • ))}

Languages

{language === 'en' ? selectedProvince!.mainLanguages : selectedProvince!.mainLanguagesNe}

)}
)} {/* Locate Button */}
); }; export default HeritageMap;