<template>
	<div v-if="isOpen" id="leaflet-map-panel" class="space-y-2">
		<div class="relative w-full h-96 md:h-160 rounded-2xl overflow-hidden border border-zinc-300 shadow-sm">
			<div ref="mapContainer" class="absolute inset-0"></div>
			<!-- map-polish:4 — Locate-me floating button -->
			<button
				type="button"
				aria-label="Show my location"
				class="absolute bottom-4 right-4 z-[900] inline-flex items-center justify-center w-10 h-10 rounded-full bg-white border border-zinc-200 text-zinc-700 shadow-md hover:bg-zinc-50 active:scale-95 transition cursor-pointer"
				@click="onLocateClick"
			>
				<iconify-icon icon="lucide:locate-fixed" class="text-lg"></iconify-icon>
			</button>
			<slot name="overlay" />
		</div>

		<div class="flex flex-wrap gap-x-3 gap-y-1 text-xs text-zinc-500">
			<span class="flex items-center gap-1.5">
				<span class="inline-block size-3 rounded-full bg-green-500"></span>
				Below average
			</span>
			<span class="flex items-center gap-1.5">
				<span class="inline-block size-3 rounded-full bg-slate-500"></span>
				Around average
			</span>
			<span class="flex items-center gap-1.5">
				<span class="inline-block size-3 rounded-full bg-red-500"></span>
				Above average
			</span>
		</div>
	</div>
</template>

<script setup>
import {ref, watch, onMounted, onUnmounted, nextTick} from 'vue'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'

const MARKER_FOCUS_ZOOM = 13 // map-polish:6 — minimum zoom when focusing a marker

const DEAL_COLOURS = {
	cheap: '#22c55e',     // green — below market
	average: '#64748b',   // slate — around market
	expensive: '#dc2626', // red — above market
}

const DEAL_BORDER_COLOURS = {
	cheap: '#16a34a',
	average: '#475569',
	expensive: '#991b1b',
}

function buildDirectionsUrl(station, origin) {
	const base = `https://www.google.com/maps/dir/?api=1&destination=${station.lat},${station.lng}`
	if (origin?.lat != null && origin?.lng != null) {
		return `${base}&origin=${origin.lat},${origin.lng}`
	}
	return base
}

function buildMarkerHtml(station, index, colour, borderColour, isSelected = false) {
	const isFirst = index === 0
	const w = isFirst ? 46 : 40
	const h = isFirst ? 20 : 18
	const fontSize = isFirst ? 11 : 10
	const star = isFirst
		? `<span style="margin-right:2px;color:#facc15;font-size:10px;line-height:1;">★</span>`
		: ''

	const ringStyle = isSelected
		? 'box-shadow:0 0 0 3px rgba(187,91,62,0.35),0 1px 3px rgba(0,0,0,0.25);transform:scale(1.12);'
		: 'box-shadow:0 1px 3px rgba(0,0,0,0.25);'

	return `<div style="display:inline-flex;align-items:center;justify-content:center;width:${w}px;height:${h}px;padding:0 5px;background:${colour};color:#fff;font-weight:700;font-size:${fontSize}px;line-height:1;letter-spacing:-0.2px;border-radius:10px;border:1.5px solid ${borderColour};${ringStyle}white-space:nowrap;transition:transform .15s ease,box-shadow .15s ease;">
        ${star}${Number(station.price).toFixed(1)}
    </div>`
}

function buildStationIcon(station, index, isSelected) {
	const isFirst = index === 0
	const w = isFirst ? 46 : 40
	const h = isFirst ? 20 : 18
	const colour = DEAL_COLOURS[station.deal_quality] ?? DEAL_COLOURS.average
	const borderColour = DEAL_BORDER_COLOURS[station.deal_quality] ?? DEAL_BORDER_COLOURS.average
	return L.divIcon({
		className: '',
		iconSize: [w, h],
		iconAnchor: [w / 2, h / 2],
		html: buildMarkerHtml(station, index, colour, borderColour, isSelected),
	})
}

function escHtml(str) {
	return String(str ?? '')
		.replace(/&/g, '&amp;')
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
		.replace(/"/g, '&quot;')
}

const props = defineProps({
	stations: {type: Array, required: true},
	isOpen: {type: Boolean, default: true},
	radiusMiles: {type: Number, default: 10},
	origin: {type: Object, default: null},
	selectedStationId: {type: String, default: null},
})

const emit = defineEmits(['station-select'])

const mapContainer = ref(null)
let mapInstance = null
let markersLayer = null
let userMarker = null
let userAccuracyCircle = null // map-polish:4
let hasInitialView = false   // map-polish:6 — flyTo needs an existing view
const markerByStationId = new Map()

function getZoomForRadius(radiusMiles) {
	if (radiusMiles <= 1) return 16
	if (radiusMiles <= 2) return 15
	if (radiusMiles <= 5) return 14
	if (radiusMiles <= 10) return 11
	if (radiusMiles <= 15) return 11
	if (radiusMiles <= 20) return 10
	if (radiusMiles <= 25) return 10
	if (radiusMiles <= 50) return 9
	return 8
}


// map-polish:4 — accuracy ring rendered alongside the user dot
function addUserMarker(lat, lng, accuracyMeters = null) {
	if (userMarker) userMarker.remove()
	if (userAccuracyCircle) userAccuracyCircle.remove()

	const icon = L.divIcon({
		html: `
			<div class="fa-user-loc">
				<div class="fa-user-loc-pulse"></div>
				<div class="fa-user-loc-dot"></div>
			</div>
		`,
		className: 'fa-user-loc-icon',
		iconSize: [22, 22],
		iconAnchor: [11, 11],
	})

	userMarker = L.marker([lat, lng], {icon, zIndexOffset: -100, interactive: false})
		.addTo(mapInstance)

	if (accuracyMeters && accuracyMeters > 0) {
		userAccuracyCircle = L.circle([lat, lng], {
			radius: accuracyMeters,
			color: '#3b82f6',
			weight: 1,
			opacity: 0.4,
			fillColor: '#3b82f6',
			fillOpacity: 0.08,
			interactive: false,
		}).addTo(mapInstance)
	}
}

function locateUser({pan = false} = {}) {
	if (!navigator.geolocation) return

	const onSuccess = (pos) => {
		const {latitude, longitude, accuracy} = pos.coords
		addUserMarker(latitude, longitude, accuracy)
		// map-polish:6 — smooth flyTo when explicitly locating the user
		if (pan && mapInstance) {
			const zoom = hasInitialView ? Math.max(mapInstance.getZoom(), 13) : 13
			if (hasInitialView) {
				mapInstance.flyTo([latitude, longitude], zoom, {duration: 0.8})
			} else {
				mapInstance.setView([latitude, longitude], zoom)
				hasInitialView = true
			}
		}
	}

	const ipFallback = () => {
		fetch('https://ipapi.co/json/')
			.then(r => r.json())
			.then(d => d.latitude && d.longitude && addUserMarker(d.latitude, d.longitude))
			.catch(() => {})
	}

	navigator.geolocation.getCurrentPosition(
		onSuccess,
		() => ipFallback(),
		{enableHighAccuracy: true, timeout: 10000, maximumAge: 30000},
	)
}

function onLocateClick() {
	locateUser({pan: true})
}

function initMap() {
	if (mapInstance || !mapContainer.value) return

	// map-polish:7 — replace default attribution control with custom ⓘ button
	mapInstance = L.map(mapContainer.value, {zoomControl: false, attributionControl: false})

	// Set the initial view immediately so tiles load at the correct spot from
	// frame 1 — avoids the "wiggle" caused by setView running after the
	// container is already laid out and tiles are mid-load.
	const initialZoom = getZoomForRadius(props.radiusMiles)
	const initialCenter = props.origin?.lat != null && props.origin?.lng != null
		? [props.origin.lat, props.origin.lng]
		: props.stations.length
			? [props.stations[0].lat, props.stations[0].lng]
			: null
	if (initialCenter) {
		mapInstance.setView(initialCenter, initialZoom)
		hasInitialView = true
	}

	// map-polish:5 — Carto Positron tile (cleaner than raw OSM)
	L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
		subdomains: 'abcd',
		maxZoom: 19,
		attribution: '&copy; OpenStreetMap contributors &copy; CARTO',
	}).addTo(mapInstance)

	// map-polish:7 — small ⓘ control with attribution on click/hover
	const attribCtl = L.control({position: 'bottomleft'})
	attribCtl.onAdd = () => {
		const wrap = L.DomUtil.create('div', 'fa-attrib')
		wrap.innerHTML = `
			<button type="button" class="fa-attrib-btn" aria-label="Map attribution">i</button>
			<div class="fa-attrib-popover" role="tooltip">&copy; OpenStreetMap contributors &copy; CARTO</div>
		`
		L.DomEvent.disableClickPropagation(wrap)
		L.DomEvent.disableScrollPropagation(wrap)
		return wrap
	}
	attribCtl.addTo(mapInstance)

	markersLayer = L.layerGroup().addTo(mapInstance)

	locateUser()
}

function renderMarkers({skipRecenter = false} = {}) {
	if (!mapInstance || !markersLayer) return

	markersLayer.clearLayers()
	markerByStationId.clear()

	if (!props.stations.length) return

	const bounds = []

	props.stations.forEach((station, index) => {
		const isSelected = props.selectedStationId === station.station_id
		const icon = buildStationIcon(station, index, isSelected)
		const marker = L.marker([station.lat, station.lng], {icon, _stationIndex: index})

		marker.on('click', () => {
			emit('station-select', station.station_id)
			const target = Math.max(mapInstance.getZoom(), MARKER_FOCUS_ZOOM)
			// map-polish:6 — flyTo for smooth marker focus (view always set by now)
			if (hasInitialView) {
				mapInstance.flyTo([station.lat, station.lng], target, {duration: 0.6})
			} else {
				mapInstance.setView([station.lat, station.lng], target)
				hasInitialView = true
			}
		})

		markersLayer.addLayer(marker)
		markerByStationId.set(station.station_id, {marker, station, index})
		bounds.push([station.lat, station.lng])
	})

	if (skipRecenter) return

	const zoom = getZoomForRadius(props.radiusMiles)
	const center = props.origin?.lat != null && props.origin?.lng != null
		? [props.origin.lat, props.origin.lng]
		: bounds[0]

	// map-polish:6 — flyTo only after the initial view exists
	if (hasInitialView) {
		mapInstance.flyTo(center, zoom, {duration: 0.6})
	} else {
		mapInstance.setView(center, zoom)
		hasInitialView = true
	}
}

function destroyMap() {
	if (mapInstance) {
		mapInstance.remove()
		mapInstance = null
		markersLayer = null
		userMarker = null
		userAccuracyCircle = null
		hasInitialView = false
		markerByStationId.clear()
	}
}

async function openMap() {
	await nextTick()
	const wasFreshInit = !mapInstance
	initMap()
	mapInstance?.invalidateSize()
	renderMarkers({skipRecenter: wasFreshInit})
}

onMounted(() => {
	if (props.isOpen) openMap()
})

watch(() => props.isOpen, (open) => {
	if (open) openMap()
	else destroyMap()
})

watch(() => props.stations, () => {
	if (props.isOpen) {
		renderMarkers()
	}
})

watch(() => props.selectedStationId, (newId, oldId) => {
	if (!mapInstance) return

	if (oldId) {
		const prev = markerByStationId.get(oldId)
		if (prev) prev.marker.setIcon(buildStationIcon(prev.station, prev.index, false))
	}
	if (newId) {
		const next = markerByStationId.get(newId)
		if (next) {
			next.marker.setIcon(buildStationIcon(next.station, next.index, true))
			if (hasInitialView) {
				const ll = next.marker.getLatLng()
				mapInstance.flyTo(ll, Math.max(mapInstance.getZoom(), MARKER_FOCUS_ZOOM), {duration: 0.5})
			}
		}
	}
})

onUnmounted(destroyMap)
</script>

<style>
/* map-polish:4 — user-location dot + pulse */
.fa-user-loc-icon { background: transparent; border: none; }
.fa-user-loc {
	position: relative;
	width: 22px;
	height: 22px;
}
.fa-user-loc-dot {
	position: absolute;
	inset: 6px;
	background: #2563eb;
	border-radius: 50%;
	border: 2px solid #fff;
	box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.fa-user-loc-pulse {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: rgba(37, 99, 235, 0.25);
	animation: fa-user-pulse 2s ease-out infinite;
}
@keyframes fa-user-pulse {
	0%   { transform: scale(0.6); opacity: 0.8; }
	100% { transform: scale(2.2); opacity: 0; }
}

/* map-polish:7 — discrete attribution control */
.fa-attrib { position: relative; }
.fa-attrib-btn {
	width: 22px;
	height: 22px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 9999px;
	background: rgba(255, 255, 255, 0.92);
	border: 1px solid rgba(0, 0, 0, 0.08);
	color: #6b7280;
	font: 600 11px/1 ui-sans-serif, system-ui, sans-serif;
	cursor: pointer;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.fa-attrib-btn:hover { color: #111827; }
.fa-attrib-popover {
	position: absolute;
	left: 0;
	bottom: calc(100% + 6px);
	white-space: nowrap;
	background: rgba(255, 255, 255, 0.95);
	color: #4b5563;
	font-size: 10px;
	padding: 4px 8px;
	border-radius: 6px;
	border: 1px solid rgba(0, 0, 0, 0.08);
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
	opacity: 0;
	pointer-events: none;
	transform: translateY(2px);
	transition: opacity .15s ease, transform .15s ease;
}
.fa-attrib:hover .fa-attrib-popover,
.fa-attrib:focus-within .fa-attrib-popover {
	opacity: 1;
	transform: translateY(0);
}
</style>
