import React, { useState, useEffect, useRef } from 'react';
import { MapContainer, TileLayer, Marker, Polyline, Circle, useMap } from 'react-leaflet';
import {
Navigation,
ShieldAlert,
MapPin,
Volume2,
Share2,
Activity,
Clock,
} from 'lucide-react';
import { motion } from 'framer-motion';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
const API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8000';
// --- ICONS: user blue dot ---
const createBlueDotIcon = () =>
L.divIcon({
className: 'custom-div-icon',
html: `
`,
iconSize: [20, 20],
iconAnchor: [10, 10],
});
// --- ICONS: radar marker for zones (danger/crowded/safe) ---
const createRadarIcon = (type) => {
const color =
type === 'danger' ? '#EF4444' : type === 'crowded' ? '#EAB308' : '#22C55E';
return L.divIcon({
className: 'custom-radar',
html: `
`,
iconSize: [20, 20],
iconAnchor: [10, 10],
});
};
// --- Map controller: smoothly fly to center + zoom ---
const MapController = ({ center, zoom }) => {
const map = useMap();
useEffect(() => {
if (center) {
map.flyTo(center, zoom || 13, { animate: true, duration: 1.5 });
}
}, [center, zoom, map]);
return null;
};
const MapScreen = () => {
// --- STATIC USER LOCATION (for demo) ---
// Change STATIC_POS if you want a different default user location
const STATIC_POS = [24.7150, 46.6800];
const [userPos] = useState(STATIC_POS);
const [mapCenter, setMapCenter] = useState(STATIC_POS);
const [mapZoom, setMapZoom] = useState(12);
// --- BACKEND DATA ---
const [zones, setZones] = useState([]);
const [routeLine, setRouteLine] = useState([]);
const [targetZone, setTargetZone] = useState(null);
const [distanceKm, setDistanceKm] = useState(null);
// --- AI STATE (reason + step-by-step instructions) ---
const [aiReason, setAiReason] = useState('');
const [instructionsList, setInstructionsList] = useState([]);
const [currentInstIndex, setCurrentInstIndex] = useState(0);
const [etaMinutes, setEtaMinutes] = useState(null);
// --- AUDIO STATE ---
const [isPlaying, setIsPlaying] = useState(false);
const audioRef = useRef(null);
// --- UI STATE ---
const [isOpen, setIsOpen] = useState(true); // bottom sheet open/closed
const [isSharing, setIsSharing] = useState(false); // SOS button loading
const [sosData, setSosData] = useState(null); // SOS toast data
const [isNavigating, setIsNavigating] = useState(false); // mini-nav mode
// --- Fetch zones from backend (fire expansion, etc.) ---
useEffect(() => {
const fetchZones = async () => {
try {
const res = await fetch(`${API_BASE}/map-data`);
const data = await res.json();
setZones(data);
} catch (e) {
console.error('Error loading zones:', e);
}
};
fetchZones();
// Refresh zones every 5 seconds to reflect dynamic hazard changes
const interval = setInterval(fetchZones, 5000);
return () => clearInterval(interval);
}, []);
// --- Rotate instructions (top banner) every 6 seconds ---
useEffect(() => {
if (instructionsList.length > 0) {
const interval = setInterval(() => {
setCurrentInstIndex((prev) => (prev + 1) % instructionsList.length);
}, 6000);
return () => clearInterval(interval);
}
}, [instructionsList]);
// --- Handle audio ended ---
useEffect(() => {
if (audioRef.current) {
audioRef.current.onended = () => setIsPlaying(false);
}
}, [audioRef.current]);
// --- Start navigation once on mount (using static user position) ---
useEffect(() => {
startNavigation();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// --- Call /navigate to get safest target zone + route + AI explanation + TTS ---
const startNavigation = async () => {
try {
setAiReason('جاري تحليل الموقف...');
const response = await fetch(`${API_BASE}/navigate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lat: userPos[0], lng: userPos[1] }),
});
const data = await response.json();
setTargetZone(data.target_zone);
setRouteLine(data.route || []);
setDistanceKm(data.meta?.distance_km ?? null);
if (data.ai_response) {
setAiReason(data.ai_response.reasoning || '');
setInstructionsList(data.ai_response.instructions_list || []);
setEtaMinutes(data.ai_response.eta_minutes || null);
}
setIsOpen(true);
// Prepare audio from base64 if available
if (data.audio_base64) {
const audioSrc = `data:audio/mpeg;base64,${data.audio_base64}`;
audioRef.current = new Audio(audioSrc);
audioRef.current.onended = () => setIsPlaying(false);
console.log('✅ Audio ready');
} else {
console.warn('⚠️ No audio_base64 from backend');
}
} catch (error) {
console.error(error);
alert('خطأ في الاتصال مع خادم الإخلاء');
}
};
// --- Toggle audio play/pause from Volume icon ---
const toggleAudio = (e) => {
e.stopPropagation();
if (!audioRef.current) {
console.warn('⚠️ Audio not ready yet');
return;
}
if (isPlaying) {
audioRef.current.pause();
setIsPlaying(false);
} else {
audioRef.current
.play()
.then(() => setIsPlaying(true))
.catch((err) => console.error('Audio play error:', err));
}
};
// --- Start navigation: collapse sheet to mini-nav, zoom to user, play audio ---
const handleStart = (e) => {
e.stopPropagation();
setIsOpen(false);
setIsNavigating(true);
// Focus map on user location
setMapCenter(userPos);
setMapZoom(16);
// Play from the beginning
if (audioRef.current) {
audioRef.current.currentTime = 0;
audioRef.current
.play()
.then(() => setIsPlaying(true))
.catch((err) => console.error('Audio play error:', err));
} else {
console.warn('⚠️ Audio not ready when pressing start');
}
};
// --- Stop navigation: return to full bottom sheet, stop audio ---
const handleStopNavigation = (e) => {
e.stopPropagation();
setIsNavigating(false);
setIsOpen(true);
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
setIsPlaying(false);
}
};
// --- Send SOS to /alert-911 (Fire Dept / Command Center) ---
const handleShare = async (e) => {
e.stopPropagation();
setIsSharing(true);
try {
const response = await fetch(`${API_BASE}/alert-911`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lat: userPos[0], lng: userPos[1] }),
});
const data = await response.json();
setSosData(data);
setTimeout(() => setSosData(null), 5000);
} catch (error) {
console.error('SOS error:', error);
} finally {
setIsSharing(false);
}
};
// --- Distance text for bottom sheet ---
const distanceText =
distanceKm != null
? `يبعد تقريبًا ${distanceKm.toFixed(1)} كم عن موقعك`
: '';
// Distance as plain number for mini navigation UI
const distance = distanceKm != null ? distanceKm.toFixed(1) : '';
// Current time (for mini navigation view)
const getCurrentTime = () => {
const now = new Date();
return now.toLocaleTimeString('ar-SA', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
};
// --- Expanding hazard zones (animated red auras around specific ids) ---
const dangerZones = zones.filter((z) => z.type === 'danger');
const expandingZones = dangerZones.filter((z) =>
['z_danger_1', 'z_danger_2', 'z_danger_3'].includes(z.id)
);
return (
{/* MAP LAYER */}
{/* Expanding hazard circles for key danger zones */}
{targetZone &&
expandingZones.map((zone) => {
let expansionMultiplier = 1.0;
if (zone.id === 'z_danger_1') expansionMultiplier = 1.0;
else if (zone.id === 'z_danger_2') expansionMultiplier = 0.7;
else if (zone.id === 'z_danger_3') expansionMultiplier = 1.05;
const currentRadius = (zone.radius || 1) * expansionMultiplier;
return (
);
})}
{/* Zone markers */}
{zones.map((zone) => (
))}
{/* Evacuation route polyline */}
{routeLine.length > 0 && (
)}
{/* Static user location marker */}
{/* Map controller for smooth fly-to transitions */}
{/* TOP: Emergency banner + rotating instructions */}
حالة طوارئ نشطة
{instructionsList.length > 0
? instructionsList[currentInstIndex]
: 'إخلاء قيد التحليل...'}
{/* SOS TOAST */}
{sosData && (
تم إرسال البلاغ
{sosData.message}
)}
{/* BOTTOM SHEET */}
!isNavigating && setIsOpen(!isOpen)}
className={`bg-white/98 backdrop-blur-xl rounded-t-[28px] shadow-[0_-4px_24px_rgba(0,0,0,0.12)] px-5 py-4 h-full border-t-2 border-gray-100 ${
!isNavigating ? 'cursor-pointer' : ''
}`}
>
{/* handle for dragging */}
{!isNavigating && (
)}
{targetZone ? (
isNavigating ? (
// --- MINI NAVIGATION VIEW (compact mode while walking) ---
{/* Time & ETA */}
{etaMinutes ?? '--'}
دقيقة
{distance && (
<>
{distance} كم
•
>
)}
{getCurrentTime()}
{/* Direction arrow placeholder (could be replaced by heading) */}
{/* AUDIO TOGGLE BUTTON */}
{/* STOP NAV BUTTON */}
) : (
// --- FULL EXPANDED VIEW (before starting navigation) ---
<>
{targetZone.name}
المنطقة الآمنة المختارة
{distanceText && (
{distanceText}
)}
{etaMinutes && (
الوقت التقديري للوصول: {etaMinutes} دقيقة
)}
{/* AUDIO TOGGLE BUTTON */}
{/* Reason / explanation card */}
سبب اختيار المسار
{aiReason || 'يتم الآن توليد تفسير المسار الآمن...'}
{/* Actions: SOS + Start */}
>
)
) : (
// --- LOADING STATE: waiting for /navigate ---
جاري تحليل مسار الإخلاء
يعمل المساعد الذكي "إخلاء" الآن على اختيار أفضل مسار آمن لك...
)}
);
};
export default MapScreen;