#!/bin/bash
# Generates map.html: GPX track + photo POIs, photos within 50 m clustered into one marker.
# Tools: xmlstarlet (GPX), exiv2 (EXIF), gawk, date, sed

set -euo pipefail

DIR="${1:-.}"
GPX="$DIR/test.gpx"
OUT="$DIR/map.html"
TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT

GPX_NS="http://www.topografix.com/GPX/1/1"
PHOTO_TZ="+0200"       # CEST (Switzerland, June)
CLUSTER_RADIUS=50      # metres

# ---------------------------------------------------------------------------
# 1. Parse GPX with xmlstarlet → epochs via gawk
#    TMP/track.txt: "epoch lat lon"
# ---------------------------------------------------------------------------
echo "Parsing GPX track..."

xmlstarlet sel -N gpx="$GPX_NS" \
    -t -m "//gpx:trkpt" \
    -v "@lat" -o " " -v "@lon" -o " " -v "gpx:time" -n \
    "$GPX" 2>/dev/null \
| TZ=UTC gawk '
    function to_epoch(ts,    y,mo,d,h,mi,s) {
        y=substr(ts,1,4)+0; mo=substr(ts,6,2)+0; d=substr(ts,9,2)+0
        h=substr(ts,12,2)+0; mi=substr(ts,15,2)+0; s=substr(ts,18,2)+0
        return mktime(y" "mo" "d" "h" "mi" "s)
    }
    { ep=to_epoch($3); if (ep>0) print ep, $1, $2 }
' | sort -n > "$TMP/track.txt"

NPTS=$(wc -l < "$TMP/track.txt")
GPX_START=$(awk 'NR==1 {print $1}' "$TMP/track.txt")
GPX_END=$(  awk 'END   {print $1}' "$TMP/track.txt")
echo "  $NPTS trackpoints  ($(date -d "@$GPX_START" '+%Y-%m-%d %H:%M') → $(date -d "@$GPX_END" '+%Y-%m-%d %H:%M') UTC)"

# ---------------------------------------------------------------------------
# 2. Correlate photos to nearest GPX point by timestamp
#    TMP/markers.txt: "lat|lon|filename|timestamp"
# ---------------------------------------------------------------------------
echo "Processing photos..."
> "$TMP/markers.txt"

for f in "$DIR"/*.JPG; do
    [ -f "$f" ] || continue
    base=$(basename "$f")

    ts=$(exiv2 "$f" 2>/dev/null | awk '/Image timestamp/ {print $4, $5}')
    [ -z "$ts" ] && { echo "  SKIP (no EXIF): $base"; continue; }

    ts_iso=$(echo "$ts" | sed 's/^\([0-9]\{4\}\):\([0-9]\{2\}\):\([0-9]\{2\}\)/\1-\2-\3/')
    epoch=$(date -d "$ts_iso $PHOTO_TZ" +%s 2>/dev/null) || { echo "  SKIP (bad time): $base"; continue; }

    if [ "$epoch" -lt "$GPX_START" ] || [ "$epoch" -gt "$GPX_END" ]; then
        echo "  SKIP (outside track): $base"
        continue
    fi

    read -r lat lon <<< "$(awk -v t="$epoch" '
        BEGIN { best=999999999 }
        { d=$1-t; if(d<0)d=-d; if(d<best){best=d;lat=$2;lon=$3} }
        END { print lat, lon }
    ' "$TMP/track.txt")"

    [ -z "$lat" ] && continue
    printf '%s|%s|%s|%s\n' "$lat" "$lon" "$base" "$ts_iso" >> "$TMP/markers.txt"
done

NM=$(wc -l < "$TMP/markers.txt")
echo "  $NM photos correlated to track"

# ---------------------------------------------------------------------------
# 3. Cluster photos within CLUSTER_RADIUS metres
#    Each cluster → TMP/clusters/<id>.txt with pipe-separated entries
# ---------------------------------------------------------------------------
echo "Clustering (radius=${CLUSTER_RADIUS} m)..."
mkdir -p "$TMP/clusters"
> "$TMP/cluster_centers.txt"
CID=0

while IFS='|' read -r lat lon base ts; do
    # Find first cluster center within radius
    found=$(awk -v la="$lat" -v lo="$lon" -v r="$CLUSTER_RADIUS" '
        function hav(la1,lo1,la2,lo2,    R,pi,dlat,dlon,a) {
            R=6371000; pi=3.14159265358979
            dlat=(la2-la1)*pi/180; dlon=(lo2-lo1)*pi/180
            la1=la1*pi/180; la2=la2*pi/180
            a=sin(dlat/2)^2+cos(la1)*cos(la2)*sin(dlon/2)^2
            return 2*R*atan2(sqrt(a),sqrt(1-a))
        }
        { if (hav($2,$3,la,lo)<=r) { print $1; exit } }
    ' "$TMP/cluster_centers.txt")

    if [ -n "$found" ]; then
        printf '%s|%s|%s|%s\n' "$lat" "$lon" "$base" "$ts" >> "$TMP/clusters/$found.txt"
    else
        CID=$((CID+1))
        echo "$CID $lat $lon" >> "$TMP/cluster_centers.txt"
        printf '%s|%s|%s|%s\n' "$lat" "$lon" "$base" "$ts" > "$TMP/clusters/$CID.txt"
    fi
done < "$TMP/markers.txt"

NC=$(ls "$TMP/clusters/" | wc -l)
echo "  $NM photos → $NC POIs"

# ---------------------------------------------------------------------------
# 4. Generate HTML
# ---------------------------------------------------------------------------
echo "Generating $OUT ..."

{
cat << 'HEADER'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tour Map</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<style>
  html, body { margin: 0; padding: 0; height: 100%; font-family: sans-serif; }
  #map { height: 100vh; }

  /* Single-photo popup */
  .single-popup img {
    max-width: 300px; max-height: 220px; display: block;
    border-radius: 4px; cursor: pointer; margin-bottom: 5px;
  }
  .single-popup .cap { font-size: 12px; color: #555; }
  .single-popup .cap a { color: #1a73e8; text-decoration: none; font-weight: bold; }

  /* Multi-photo cluster popup */
  .cluster-popup h4 { margin: 0 0 8px; font-size: 13px; color: #333; }
  .thumb-grid {
    display: grid;
    grid-template-columns: repeat(3, 100px);
    gap: 6px;
    max-height: 340px;
    overflow-y: auto;
  }
  .thumb-item a { display: block; }
  .thumb-item img {
    width: 100px; height: 75px; object-fit: cover;
    border-radius: 3px; display: block; cursor: pointer;
  }
  .thumb-item img:hover { opacity: 0.85; }
  .thumb-cap { font-size: 10px; color: #666; text-align: center; margin-top: 2px; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
var map = L.map('map');

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

// GPX track
var trackPoints = [
HEADER

    awk '{printf "  [%s,%s],\n", $2, $3}' "$TMP/track.txt"

cat << 'MIDSECTION'
];

var polyline = L.polyline(trackPoints, {color:'#d94f00', weight:3, opacity:0.8}).addTo(map);
map.fitBounds(polyline.getBounds(), {padding:[40,40]});

L.circleMarker(trackPoints[0],
  {radius:7, color:'#155a15', fillColor:'#2ecc40', fillOpacity:1, weight:2})
  .addTo(map).bindPopup('<b>Start</b>');
L.circleMarker(trackPoints[trackPoints.length-1],
  {radius:7, color:'#7a1212', fillColor:'#e74c3c', fillOpacity:1, weight:2})
  .addTo(map).bindPopup('<b>End</b>');

// Icons
function makeCamIcon(n) {
  if (n === 1) {
    return L.divIcon({
      html: '<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">'
          + '<circle cx="15" cy="15" r="13" fill="#1a73e8" stroke="white" stroke-width="1.5"/>'
          + '<text x="15" y="20" text-anchor="middle" font-size="14">📷</text>'
          + '</svg>',
      className:'', iconSize:[30,30], iconAnchor:[15,15], popupAnchor:[0,-18]
    });
  }
  return L.divIcon({
    html: '<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 38 38">'
        + '<circle cx="19" cy="19" r="17" fill="#e8711a" stroke="white" stroke-width="1.5"/>'
        + '<text x="19" y="15" text-anchor="middle" font-size="13">📷</text>'
        + '<text x="19" y="28" text-anchor="middle" fill="white" font-size="12"'
        + '      font-family="sans-serif" font-weight="bold">\xd7' + n + '</text>'
        + '</svg>',
    className:'', iconSize:[38,38], iconAnchor:[19,19], popupAnchor:[0,-21]
  });
}

// Single-photo marker
function addSingle(lat, lon, file, time) {
  L.marker([lat, lon], {icon: makeCamIcon(1)}).addTo(map).bindPopup(
    '<div class="single-popup">'
    + '<a href="' + file + '" target="_blank">'
    + '<img src="' + file + '" loading="lazy" onerror="this.style.display=\'none\'">'
    + '</a>'
    + '<div class="cap"><a href="' + file + '" target="_blank">' + file + '</a><br>' + time + '</div>'
    + '</div>',
    {maxWidth: 320}
  );
}

// Clustered-photos marker — centroid position, thumbnail grid popup
function addCluster(photos) {
  var lat = 0, lon = 0;
  photos.forEach(function(p) { lat += p.lat; lon += p.lon; });
  lat /= photos.length; lon /= photos.length;

  var thumbs = '';
  photos.forEach(function(p) {
    thumbs += '<div class="thumb-item">'
            + '<a href="' + p.file + '" target="_blank" title="' + p.file + '">'
            + '<img src="' + p.file + '" loading="lazy" onerror="this.style.display=\'none\'">'
            + '</a>'
            + '<div class="thumb-cap">' + p.time.slice(11) + '</div>'
            + '</div>';
  });

  L.marker([lat, lon], {icon: makeCamIcon(photos.length)}).addTo(map).bindPopup(
    '<div class="cluster-popup">'
    + '<h4>📷 ' + photos.length + ' photos at this location</h4>'
    + '<div class="thumb-grid">' + thumbs + '</div>'
    + '</div>',
    {maxWidth: 360}
  );
}

MIDSECTION

    # Emit one JS call per cluster, sorted numerically
    for cfile in $(ls "$TMP/clusters/" | sort -V | sed "s|^|$TMP/clusters/|"); do
        count=$(wc -l < "$cfile")
        if [ "$count" -eq 1 ]; then
            IFS='|' read -r lat lon base ts < "$cfile"
            printf 'addSingle(%s, %s, "%s", "%s");\n' "$lat" "$lon" "$base" "$ts"
        else
            echo 'addCluster(['
            while IFS='|' read -r lat lon base ts; do
                printf '  {lat:%s, lon:%s, file:"%s", time:"%s"},\n' "$lat" "$lon" "$base" "$ts"
            done < "$cfile"
            echo ']);'
        fi
    done

cat << 'FOOTER'
</script>
</body>
</html>
FOOTER
} > "$OUT"

echo "Done → $OUT  ($NC POIs from $NM photos, $NPTS track points)"
