import React, { useEffect, useState, useMemo } from 'react'; import { View, Text, ScrollView, StyleSheet, TouchableOpacity, Linking } from 'react-native'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import * as Location from 'expo-location'; import { getMedicine, getMedicinePharmacies } from '../../services/medicines'; import { StockBadge } from '../../components/StockBadge'; import { LoadingSpinner } from '../../components/LoadingSpinner'; import { useThemeContext } from '../../components/ThemeProvider'; import { spacing, borderRadius } from '../../constants/theme'; import { Medicine, PharmacyMedicine } from '../../types'; function haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number { const R = 6371; const dLat = ((lat2 - lat1) * Math.PI) / 180; const dLon = ((lon2 - lon1) * Math.PI) / 180; const a = Math.sin(dLat / 2) ** 2 + Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2; return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function formatDistance(km: number): string { if (km < 1) return `${Math.round(km * 1000)} m`; if (km < 10) return `${km.toFixed(1)} km`; return `${Math.round(km)} km`; } function getPharmacyLat(p: PharmacyMedicine): number | null { return p.latitude ?? p.pharmacy?.latitude ?? null; } function getPharmacyLon(p: PharmacyMedicine): number | null { return p.longitude ?? p.pharmacy?.longitude ?? null; } export default function MedicineDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const router = useRouter(); const { colors } = useThemeContext(); const [medicine, setMedicine] = useState(null); const [pharmacies, setPharmacies] = useState([]); const [isLoading, setIsLoading] = useState(true); const [sortByDistance, setSortByDistance] = useState(false); const [userPosition, setUserPosition] = useState<{ lat: number; lon: number } | null>(null); const [locating, setLocating] = useState(false); const [locationError, setLocationError] = useState(null); useEffect(() => { if (!id) return; const fetchMedicine = async () => { try { const [medData, pharmData] = await Promise.all([ getMedicine(id), getMedicinePharmacies(id), ]); setMedicine(medData); setPharmacies(pharmData); } catch (error) { console.error('Error fetching medicine:', error); } finally { setIsLoading(false); } }; fetchMedicine(); }, [id]); const handleSortByDistance = async () => { if (sortByDistance) { setSortByDistance(false); return; } setLocating(true); setLocationError(null); try { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { setLocationError('Permiso de ubicación denegado'); setLocating(false); return; } const pos = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced }); setUserPosition({ lat: pos.coords.latitude, lon: pos.coords.longitude }); setSortByDistance(true); } catch { setLocationError('No se pudo obtener tu ubicación'); } finally { setLocating(false); } }; const sortedPharmacies = useMemo(() => { if (!sortByDistance || !userPosition) return pharmacies; return [...pharmacies] .map((p) => { const lat = getPharmacyLat(p); const lon = getPharmacyLon(p); const distance = lat != null && lon != null ? haversineKm(userPosition.lat, userPosition.lon, lat, lon) : Infinity; return { ...p, _distance: distance }; }) .sort((a, b) => a._distance - b._distance); }, [pharmacies, sortByDistance, userPosition]); const locatedPharmacies = useMemo( () => sortedPharmacies.filter( (p) => getPharmacyLat(p) != null && getPharmacyLon(p) != null ), [sortedPharmacies] ); const mapCenter = useMemo(() => { if (locatedPharmacies.length === 0) return { latitude: 40.4168, longitude: -3.7038 }; const lat = locatedPharmacies.reduce((s, p) => s + (getPharmacyLat(p) || 0), 0) / locatedPharmacies.length; const lon = locatedPharmacies.reduce((s, p) => s + (getPharmacyLon(p) || 0), 0) / locatedPharmacies.length; return { latitude: lat, longitude: lon }; }, [locatedPharmacies]); const handleDirections = (latitude: number, longitude: number) => { const url = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`; Linking.openURL(url); }; if (isLoading) { return ; } if (!medicine) { return ( Medicamento no encontrado ); } return ( {medicine.name} {medicine.stock != null && } {locatedPharmacies.length > 0 && ( Mapa próximamente… )} Farmacias ({sortedPharmacies.length}) {locating ? 'Localizando…' : sortByDistance ? 'Distancia · Reset' : 'Ordenar por distancia'} {locationError && ( {locationError} Reintentar )} {sortedPharmacies.length === 0 ? ( No hay farmacias disponibles ) : ( sortedPharmacies.map((pharm) => { const lat = getPharmacyLat(pharm); const lon = getPharmacyLon(pharm); const distanceKm = sortByDistance && userPosition && lat != null && lon != null ? haversineKm(userPosition.lat, userPosition.lon, lat, lon) : null; return ( router.push(`/pharmacy/${pharm.pharmacy_id || pharm.id}`)} > {pharm.pharmacy?.name || pharm.name} {distanceKm != null && ( {formatDistance(distanceKm)} )} {pharm.pharmacy?.address || pharm.address} {pharm.price != null ? ( {pharm.price.toFixed(2)} € ) : ( Consultar precio )} {pharm.stock > 0 && ( Stock: {pharm.stock} )} {lat != null && lon != null && ( handleDirections(lat, lon)} > Cómo llegar )} ); }) )} ); } function InfoRow({ label, value, colors }: { label: string; value: string; colors: any }) { return ( {label} {value} ); } const styles = StyleSheet.create({ container: { flex: 1, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', padding: spacing.lg, }, name: { flex: 1, fontSize: 22, fontWeight: 'bold', marginRight: spacing.sm, }, infoSection: { marginTop: spacing.sm, padding: spacing.lg, }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: spacing.sm, borderBottomWidth: 1, }, infoLabel: { fontSize: 13, flexShrink: 0, marginRight: spacing.sm, }, infoValue: { fontSize: 13, fontWeight: '500', flex: 1, textAlign: 'right', }, mapContainer: { marginTop: spacing.sm, marginHorizontal: spacing.lg, height: 200, borderRadius: borderRadius.lg, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', gap: spacing.sm, }, mapPlaceholder: { fontSize: 14, fontWeight: '500', }, pharmaciesSection: { marginTop: spacing.sm, padding: spacing.lg, }, pharmaciesHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: spacing.md, }, sectionTitle: { fontSize: 18, fontWeight: '600', }, sortButton: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, paddingHorizontal: spacing.sm + 4, paddingVertical: spacing.xs + 2, borderRadius: borderRadius.full, borderWidth: 1, }, sortButtonActive: {}, sortButtonText: { fontSize: 13, fontWeight: '600', }, sortButtonTextActive: {}, locationErrorContainer: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, padding: spacing.sm, marginBottom: spacing.sm, borderRadius: borderRadius.md, }, locationErrorText: { flex: 1, fontSize: 13, }, retryText: { fontSize: 13, fontWeight: '600', }, noPharmacies: { textAlign: 'center', padding: spacing.xl, }, pharmacyCard: { borderRadius: borderRadius.lg, marginBottom: spacing.sm, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.06, shadowRadius: 8, elevation: 2, }, pharmacyCardContent: { flexDirection: 'row', justifyContent: 'space-between', padding: spacing.md, }, pharmacyInfo: { flex: 1, }, pharmacyNameRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, pharmacyName: { flex: 1, fontSize: 16, fontWeight: '600', marginRight: spacing.sm, }, pharmacyDistance: { fontSize: 12, fontWeight: '600', paddingHorizontal: spacing.sm, paddingVertical: 2, borderRadius: borderRadius.full, overflow: 'hidden', }, pharmacyAddress: { fontSize: 14, marginTop: spacing.xs, }, pharmacyStock: { alignItems: 'flex-end', }, price: { fontSize: 16, fontWeight: '600', }, stock: { fontSize: 12, marginTop: spacing.xs, }, directionsButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, paddingVertical: spacing.sm, borderTopWidth: 1, }, directionsText: { fontSize: 14, fontWeight: '600', }, errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, errorText: { fontSize: 16, }, });