// Google Maps section — reads key from auth-gated Firestore config via RoadtripAuth.
// Each segment has a "committed" route (green, official) and an optional "pending" route
// (blue, local edits from dragging). Hitting Update segments writes the pending shape to
// Firestore as the new committed route and broadcasts the new mileage/durations to the rest
// of the app. Discard reverts pending edits without saving.

const COLOR_OFFICIAL = '#2f7a5b'; // green — committed/official route
const COLOR_PENDING  = '#3a76d8'; // blue  — locally edited, not yet saved

function resolveMapKey(auth, publicMapKey) {
  if (auth && auth.secrets && auth.secrets.gmapsKey) return auth.secrets.gmapsKey;
  return publicMapKey || "";
}

function formatDuration(totalSecs) {
  const hours = Math.floor(totalSecs / 3600);
  const mins = Math.round((totalSecs % 3600) / 60);
  return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
}

function buildDefaultSegments() {
  return [
    {
      id: 'day-1',
      day: 1,
      label: 'Day 1 · Airport to hotel',
      a: 'San Francisco International Airport (SFO)',
      z: 'Beach House Hotel, Half Moon Bay, CA',
      aName: 'SFO',
      zName: 'Beach House Hotel',
      routeHint: 'via US-1 / Pacific Coast',
      waypoints: [
        'Pier 39, San Francisco, CA',
        'Pacifica State Beach, Pacifica, CA',
      ],
    },
    {
      id: 'day-2',
      day: 2,
      label: 'Day 2 · Half Moon Bay to Monterey',
      a: 'Beach House Hotel, Half Moon Bay, CA',
      z: 'InterContinental the Clement Monterey, Cannery Row, Monterey, CA',
      aName: 'Beach House Hotel',
      zName: 'InterContinental Monterey',
      routeHint: 'coastline route on CA-1 / US-1',
      waypoints: [
        'Pigeon Point Lighthouse, Pescadero, CA',
        'Santa Cruz Beach Boardwalk, Santa Cruz, CA',
      ],
    },
    {
      id: 'day-3',
      day: 3,
      label: 'Day 3 · Monterey to Cambria',
      a: 'InterContinental the Clement Monterey, Cannery Row, Monterey, CA',
      z: 'Cambria Shores Inn, Cambria, CA',
      aName: 'InterContinental Monterey',
      zName: 'Cambria Shores Inn',
      routeHint: 'Big Sur stretch on CA-1 / US-1',
      waypoints: [
        'Bixby Creek Bridge, Monterey County, CA',
        'McWay Falls, Big Sur, CA',
      ],
    },
    {
      id: 'day-4',
      day: 4,
      label: 'Day 4 · Cambria to Solvang',
      a: 'Cambria Shores Inn, Cambria, CA',
      z: 'The Landsby, Solvang, CA',
      aName: 'Cambria Shores Inn',
      zName: 'The Landsby',
      routeHint: 'coast first, then inland to Solvang',
      waypoints: [
        'Elephant Seal Vista Point, San Simeon, CA',
        'Morro Bay, CA',
      ],
    },
    {
      id: 'day-5',
      day: 5,
      label: 'Day 5 · Solvang to Santa Monica',
      a: 'The Landsby, Solvang, CA',
      z: 'Huntley Hotel Santa Monica Beach, Santa Monica, CA',
      aName: 'The Landsby',
      zName: 'Huntley Santa Monica',
      routeHint: 'US-101 then US-1 through Malibu',
      waypoints: [
        'Stearns Wharf, Santa Barbara, CA',
        'Malibu Pier, Malibu, CA',
      ],
    },
    {
      id: 'day-6',
      day: 6,
      label: 'Day 6 · LA loop (hotel to hotel)',
      a: 'Huntley Hotel Santa Monica Beach, Santa Monica, CA',
      z: 'Huntley Hotel Santa Monica Beach, Santa Monica, CA',
      aName: 'Huntley Santa Monica',
      zName: 'Huntley Santa Monica',
      routeHint: 'day loop and return to the same hotel',
      waypoints: [
        'Warner Bros. Studio Tour Hollywood, Burbank, CA',
      ],
    },
    {
      id: 'day-7',
      day: 7,
      label: 'Day 7 · Santa Monica to LAX',
      a: 'Huntley Hotel Santa Monica Beach, Santa Monica, CA',
      z: 'Los Angeles International Airport (LAX)',
      aName: 'Huntley Santa Monica',
      zName: 'LAX',
      routeHint: 'coastal finish down US-1',
      waypoints: [
        'Manhattan Beach Pier, Manhattan Beach, CA',
      ],
    },
  ];
}

// Convert a stored/dragged waypoint (string address OR {lat,lng}) into a DirectionsRequest
// waypoint entry. We always treat saved waypoints as via-points (stopover:false) so the
// route just bends through them rather than splitting into separate legs.
function toRequestWaypoint(w) {
  if (!w) return null;
  if (typeof w === 'string') return { location: w, stopover: false };
  if (typeof w.lat === 'number' && typeof w.lng === 'number') {
    return { location: { lat: w.lat, lng: w.lng }, stopover: false };
  }
  return null;
}

// Pull every via_waypoint out of a Directions result, flattened across legs, as plain
// {lat,lng} objects suitable for Firestore.
function extractViaWaypoints(result) {
  const out = [];
  const route = result && result.routes && result.routes[0];
  if (!route || !route.legs) return out;
  route.legs.forEach((leg) => {
    (leg.via_waypoints || []).forEach((p) => {
      out.push({ lat: p.lat(), lng: p.lng() });
    });
  });
  return out;
}

function sumRouteMetrics(result) {
  let meters = 0;
  let secs = 0;
  const route = result && result.routes && result.routes[0];
  if (!route || !route.legs) return { meters, secs };
  route.legs.forEach((leg) => {
    meters += leg.distance ? leg.distance.value : 0;
    secs += leg.duration ? leg.duration.value : 0;
  });
  return { meters, secs };
}

function MapSection() {
  const mapRef = React.useRef();
  const mapObjectsRef = React.useRef({
    map: null,
    g: null,
    directionsService: null,
    // segmentEntries[i] = { id, renderer, dragListener, color, committedResult }
    segmentEntries: [],
    // markers[i] = { marker, infoWindow }
    markers: [],
    activeInfoWindow: null,
  });
  const defaultsRef = React.useRef(buildDefaultSegments());
  const buildTokenRef = React.useRef(0);

  const [mapsReady, setMapsReady] = React.useState(!!(window.google && window.google.maps));
  const [loaded, setLoaded] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [auth, setAuth] = React.useState(() => {
    if (window.RoadtripAuth && window.RoadtripAuth.getState) return window.RoadtripAuth.getState();
    return { ready: true, user: null, error: "Auth not initialized" };
  });
  // segmentPlan = current OFFICIAL/committed segment list. waypoints field holds whatever
  // was loaded from Firestore (or the string defaults if nothing saved).
  const [segmentPlan, setSegmentPlan] = React.useState(() => defaultsRef.current.map((s) => ({ ...s })));
  // pendingEdits[segId] = { waypoints:[{lat,lng}...], meters, secs } — set when user drags.
  const [pendingEdits, setPendingEdits] = React.useState({});
  const [savingCommit, setSavingCommit] = React.useState(false);
  const [commitError, setCommitError] = React.useState(null);
  const [publicMapKey, setPublicMapKey] = React.useState("");
  const [routeStats, setRouteStats] = React.useState({
    totalMilesText: window.TRIP.totalMiles,
    totalDurationText: "",
    legs: [],
    perDay: {},
  });

  const pendingIds = Object.keys(pendingEdits);
  const hasPending = pendingIds.length > 0;

  React.useEffect(() => {
    if (!window.RoadtripAuth || !window.RoadtripAuth.subscribe) return;
    return window.RoadtripAuth.subscribe(setAuth);
  }, []);

  // Load saved route shape from Firestore (public read). Anyone seeing the map should see
  // the latest committed route with the committed totals.
  React.useEffect(() => {
    let cancelled = false;
    async function loadSavedRoute() {
      if (!window.firebase || !window.firebase.firestore) return;
      try {
        const db = window.firebase.firestore();
        const snap = await db.collection("appConfig").doc("route").get();
        if (cancelled || !snap.exists) return;
        const data = snap.data() || {};
        const saved = data.segments || {};
        setSegmentPlan((prev) => prev.map((s) => {
          const entry = saved[s.id];
          if (!entry || !Array.isArray(entry.waypoints)) return s;
          // Saved waypoints from Firestore are arrays of {lat, lng} objects — overwrite the
          // string defaults so we route through the user's bent path.
          return { ...s, waypoints: entry.waypoints };
        }));
      } catch (_) {
        // Silently keep defaults if Firestore read fails.
      }
    }
    loadSavedRoute();
    return () => { cancelled = true; };
  }, []);

  // Load the public map key
  React.useEffect(() => {
    let cancelled = false;
    async function loadPublicMapKey() {
      if (!window.firebase || !window.firebase.firestore) return;
      try {
        const db = window.firebase.firestore();
        const snap = await db.collection("appConfig").doc("public").get();
        if (!cancelled && snap.exists) {
          const data = snap.data() || {};
          setPublicMapKey((data.gmapsKey || "").trim());
        }
      } catch (_) {}
    }
    loadPublicMapKey();
    return () => { cancelled = true; };
  }, []);

  React.useEffect(() => {
    if (!auth.ready) return;
    const key = resolveMapKey(auth, publicMapKey);
    if (!key) return;
    if (window.google && window.google.maps) {
      setMapsReady(true);
      return;
    }
    const existing = document.getElementById('gmaps-script');
    if (existing) {
      const loadedFor = existing.getAttribute("data-key") || "";
      if (loadedFor && loadedFor !== key) {
        setErr("Map key changed. Reload this page once to reinitialize Google Maps.");
        return;
      }
      existing.addEventListener('load', () => setMapsReady(true), { once: true });
      return;
    }
    const s = document.createElement('script');
    s.id = 'gmaps-script';
    s.setAttribute("data-key", key);
    s.src = `https://maps.googleapis.com/maps/api/js?key=${key}&libraries=geometry`;
    s.async = true;
    s.onload = () => setMapsReady(true);
    s.onerror = () => setErr('Could not load Google Maps');
    document.head.appendChild(s);
  }, [auth.ready, auth.user, auth.secrets && auth.secrets.gmapsKey, publicMapKey]);

  // Map init (once Google Maps script is ready)
  React.useEffect(() => {
    if (!mapsReady || !mapRef.current) return;
    if (mapObjectsRef.current.map) return;
    const g = window.google;
    if (!g || !g.maps) return;

    const mapStyles = [
      { elementType: 'geometry', stylers: [{ color: '#f5ebd9' }] },
      { elementType: 'labels.text.fill', stylers: [{ color: '#5a4a3a' }] },
      { elementType: 'labels.text.stroke', stylers: [{ color: '#fbf5ec' }] },
      { featureType: 'administrative', elementType: 'geometry.stroke', stylers: [{ color: '#c4a574' }] },
      { featureType: 'administrative.land_parcel', stylers: [{ visibility: 'off' }] },
      { featureType: 'landscape.natural', elementType: 'geometry', stylers: [{ color: '#ead9b8' }] },
      { featureType: 'poi', stylers: [{ visibility: 'off' }] },
      { featureType: 'poi.park', elementType: 'geometry', stylers: [{ color: '#d9c195' }] },
      { featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#d97556' }] },
      { featureType: 'road.highway', elementType: 'geometry.stroke', stylers: [{ color: '#a84f34' }] },
      { featureType: 'road.arterial', elementType: 'geometry', stylers: [{ color: '#e8a076' }] },
      { featureType: 'road.local', elementType: 'geometry', stylers: [{ color: '#f5ebd9' }] },
      { featureType: 'transit', stylers: [{ visibility: 'off' }] },
      { featureType: 'water', elementType: 'geometry', stylers: [{ color: '#a9c8c5' }] },
      { featureType: 'water', elementType: 'labels.text.fill', stylers: [{ color: '#3f6c6c' }] },
    ];

    const map = new g.maps.Map(mapRef.current, {
      styles: mapStyles,
      disableDefaultUI: true,
      zoomControl: true,
      gestureHandling: 'greedy',
      backgroundColor: '#f5ebd9',
    });

    mapObjectsRef.current.map = map;
    mapObjectsRef.current.g = g;
    mapObjectsRef.current.directionsService = new g.maps.DirectionsService();
  }, [mapsReady]);

  function clearMapRoutes() {
    const objs = mapObjectsRef.current;
    objs.segmentEntries.forEach((entry) => {
      if (entry.dragListener) {
        objs.g.maps.event.removeListener(entry.dragListener);
      }
      entry.renderer.setMap(null);
    });
    objs.segmentEntries = [];
    objs.markers.forEach((m) => {
      if (m.infoWindow) m.infoWindow.close();
      m.marker.setMap(null);
    });
    objs.markers = [];
    if (objs.activeInfoWindow) {
      objs.activeInfoWindow.close();
      objs.activeInfoWindow = null;
    }
  }

  function broadcastCommittedMetrics() {
    const objs = mapObjectsRef.current;
    const entries = objs.segmentEntries;
    if (!entries.length) return;
    let totalMeters = 0;
    let totalSecs = 0;
    const perDay = {};
    const legs = [];

    entries.forEach((entry) => {
      const result = entry.committedResult;
      const route = result && result.routes && result.routes[0];
      if (!route) return;
      const { meters, secs } = sumRouteMetrics(result);
      totalMeters += meters;
      totalSecs += secs;
      const miles = meters / 1609.344;
      const distanceText = `${Math.round(miles)} mi`;
      const durationText = formatDuration(secs);
      const segMetric = {
        day: entry.segment.day,
        from: entry.segment.a,
        to: entry.segment.z,
        distanceText,
        durationText,
        via: entry.segment.routeHint,
      };
      perDay[entry.segment.day] = segMetric;
      legs.push({
        from: entry.segment.a,
        to: entry.segment.z,
        distanceText,
        durationText,
        day: entry.segment.day,
      });
    });

    const totalMiles = totalMeters / 1609.344;
    const next = {
      totalMilesText: `${Math.round(totalMiles)} mi`,
      totalDurationText: formatDuration(totalSecs),
      legs,
      perDay,
    };
    setRouteStats(next);
    window.dispatchEvent(new CustomEvent("route-metrics-updated", { detail: next }));
  }

  function routePromise(request) {
    const svc = mapObjectsRef.current.directionsService;
    return new Promise((resolve, reject) => {
      svc.route(request, (result, status) => {
        if (status !== "OK" || !result || !result.routes || !result.routes[0]) {
          reject(new Error(`Could not build route (${status || "unknown error"})`));
          return;
        }
        resolve(result);
      });
    });
  }

  // Build all routes from the COMMITTED segmentPlan. Re-runs whenever the committed plan
  // changes (initial load from Firestore, or after a successful Update segments commit) or
  // whenever sign-in toggles draggability.
  React.useEffect(() => {
    const objs = mapObjectsRef.current;
    if (!objs.map || !objs.g) return;
    let cancelled = false;
    const token = ++buildTokenRef.current;

    async function buildRoutes() {
      setLoaded(false);
      setErr(null);
      clearMapRoutes();

      const bounds = new objs.g.maps.LatLngBounds();
      const map = objs.map;
      const placedMarkerKeys = new Set();

      function addMarker(position, labelText, popupName, scale) {
        const key = `${position.lat()},${position.lng()}`;
        if (placedMarkerKeys.has(key)) return;
        placedMarkerKeys.add(key);
        const marker = new objs.g.maps.Marker({
          position,
          map,
          label: {
            text: labelText,
            color: 'white',
            fontFamily: 'Inter, sans-serif',
            fontWeight: '700',
            fontSize: '12px',
          },
          icon: {
            path: objs.g.maps.SymbolPath.CIRCLE,
            fillColor: '#23434a',
            fillOpacity: 1,
            strokeColor: '#fff',
            strokeWeight: 2.5,
            scale: scale || 13,
          },
          title: popupName || labelText,
        });
        const infoWindow = new objs.g.maps.InfoWindow({
          content: `<div style="font-family:Inter,sans-serif;font-size:13px;font-weight:600;color:#23434a;padding:2px 4px;">${popupName || labelText}</div>`,
        });
        marker.addListener('click', () => {
          if (objs.activeInfoWindow) objs.activeInfoWindow.close();
          infoWindow.open({ map, anchor: marker });
          objs.activeInfoWindow = infoWindow;
        });
        objs.markers.push({ marker, infoWindow });
      }

      // Fire all segment route requests in parallel — they're independent. This is the
      // single biggest win for cold-load time: ~7 sequential HTTP round trips become one
      // concurrent batch.
      const routeResults = await Promise.all(segmentPlan.map((segment) => {
        const requestWaypoints = (segment.waypoints || [])
          .map(toRequestWaypoint)
          .filter(Boolean);
        const request = {
          origin: segment.a,
          destination: segment.z,
          waypoints: requestWaypoints,
          optimizeWaypoints: false,
          travelMode: objs.g.maps.TravelMode.DRIVING,
        };
        return routePromise(request).then(
          (result) => ({ ok: true, result }),
          (e) => ({ ok: false, error: e })
        );
      }));
      if (cancelled || token !== buildTokenRef.current) return;

      // Now stitch results back into renderers in order, so segmentEntries stays sorted
      // by day and markers/legs render in trip order.
      for (let i = 0; i < segmentPlan.length; i += 1) {
        const segment = segmentPlan[i];
        const outcome = routeResults[i];

        const renderer = new objs.g.maps.DirectionsRenderer({
          map,
          suppressMarkers: true,
          draggable: !!auth.user,
          preserveViewport: true,
          polylineOptions: {
            strokeColor: COLOR_OFFICIAL,
            strokeOpacity: 0.9,
            strokeWeight: 4,
          },
        });

        if (!outcome.ok) {
          const e = outcome.error;
          setErr(`${segment.label}: ${e && e.message ? e.message : "Route error"}`);
          renderer.setMap(null);
          continue;
        }
        const result = outcome.result;

        renderer.setDirections(result);
        const route = result.routes[0];
        if (route.bounds) bounds.union(route.bounds);

        const startLeg = route.legs[0];
        const endLeg = route.legs[route.legs.length - 1];

        // Marker at the start of day 1 only — every other day's start equals the prior
        // day's end (already placed below).
        if (i === 0 && startLeg && startLeg.start_location) {
          addMarker(startLeg.start_location, 'A', segment.aName || segment.a);
        }
        // Marker at each segment's end. We dedupe by lat/lng so day-6's hotel-to-hotel
        // loop doesn't drop a duplicate pin on top of day-5's hotel marker.
        if (endLeg && endLeg.end_location) {
          const isFinal = i === segmentPlan.length - 1;
          const labelText = isFinal ? 'Z' : String(segment.day);
          addMarker(endLeg.end_location, labelText, segment.zName || segment.z);
        }

        // Build the entry first so the drag listener can reference it via closure (so
        // it can check the suppress flag before treating an event as a real drag).
        const entry = {
          segment,
          renderer,
          dragListener: null,
          color: COLOR_OFFICIAL,
          committedResult: result, // cached so Discard can revert without re-fetching
          suppressDragEvent: false, // set true around our own setDirections() calls
        };

        // Drag → capture new shape into pendingEdits + flip color to blue. We MUST guard
        // against our own setDirections() calls (used to repaint or revert) which also
        // fire `directions_changed`; without the flag, repainting blue would re-add the
        // pending edit and loop.
        entry.dragListener = renderer.addListener("directions_changed", () => {
          if (entry.suppressDragEvent) return;
          const nextResult = renderer.getDirections();
          if (!nextResult) return;
          const newWaypoints = extractViaWaypoints(nextResult);
          const { meters, secs } = sumRouteMetrics(nextResult);
          setPendingEdits((prev) => ({
            ...prev,
            [segment.id]: { waypoints: newWaypoints, meters, secs },
          }));
        });

        objs.segmentEntries.push(entry);
      }

      if (!cancelled && token === buildTokenRef.current) {
        if (!bounds.isEmpty()) {
          objs.map.fitBounds(bounds, { top: 30, right: 30, bottom: 30, left: 30 });
        }
        broadcastCommittedMetrics();
        setLoaded(true);
      }
    }

    buildRoutes();
    return () => {
      cancelled = true;
    };
  }, [segmentPlan, auth.user, mapsReady]);

  // Sync polyline color with pendingEdits without rebuilding renderers.
  // setOptions + setDirections(getDirections()) is the documented way to force the
  // renderer to redraw its polyline using updated polylineOptions.
  React.useEffect(() => {
    const objs = mapObjectsRef.current;
    if (!objs.g || !objs.segmentEntries.length) return;
    objs.segmentEntries.forEach((entry) => {
      const isPending = !!pendingEdits[entry.segment.id];
      const desired = isPending ? COLOR_PENDING : COLOR_OFFICIAL;
      if (entry.color === desired) return;
      entry.renderer.setOptions({
        polylineOptions: {
          strokeColor: desired,
          strokeOpacity: 0.9,
          strokeWeight: 4,
        },
      });
      const current = entry.renderer.getDirections();
      if (current) {
        entry.suppressDragEvent = true;
        entry.renderer.setDirections(current);
        entry.suppressDragEvent = false;
      }
      entry.color = desired;
    });
  }, [pendingEdits]);

  const hasKey = !!resolveMapKey(auth, publicMapKey);

  async function commitPending() {
    if (!hasPending || savingCommit) return;
    if (!auth.user) {
      setCommitError("Sign in to save route changes.");
      return;
    }
    setSavingCommit(true);
    setCommitError(null);

    const objs = mapObjectsRef.current;

    // Build the next segmentPlan: for each pending segment, swap in the new waypoints.
    // Also snapshot the renderer's current result as the new committedResult so the next
    // build effect doesn't re-fetch.
    const nextPlan = segmentPlan.map((s) => {
      const pending = pendingEdits[s.id];
      if (!pending) return s;
      return { ...s, waypoints: pending.waypoints };
    });

    // Also update the cached committedResult on each affected entry so broadcast metrics
    // reflect the just-committed shape immediately.
    objs.segmentEntries.forEach((entry) => {
      if (pendingEdits[entry.segment.id]) {
        const current = entry.renderer.getDirections();
        if (current) entry.committedResult = current;
      }
    });

    // Persist to Firestore so other viewers see the new official route.
    try {
      const db = window.firebase.firestore();
      const segmentsField = {};
      nextPlan.forEach((s) => {
        const entry = objs.segmentEntries.find((e) => e.segment.id === s.id);
        const m = entry ? sumRouteMetrics(entry.committedResult) : { meters: 0, secs: 0 };
        segmentsField[s.id] = {
          a: s.a,
          z: s.z,
          waypoints: s.waypoints,
          meters: m.meters,
          secs: m.secs,
        };
      });
      await db.collection("appConfig").doc("route").set({
        segments: segmentsField,
        updatedAt: window.firebase.firestore.FieldValue.serverTimestamp(),
        updatedBy: auth.user.email || null,
      });
    } catch (e) {
      setCommitError((e && e.message) || "Could not save route. Check your connection and try again.");
      setSavingCommit(false);
      return;
    }

    setPendingEdits({});
    setSegmentPlan(nextPlan);
    setSavingCommit(false);
    // The build effect re-runs on segmentPlan change and will broadcast fresh metrics.
  }

  function discardPending() {
    if (!hasPending) return;
    const objs = mapObjectsRef.current;
    // Revert each affected renderer to its cached committed result. No API call needed.
    // Suppress the drag listener so our own setDirections doesn't re-add the pending edit.
    objs.segmentEntries.forEach((entry) => {
      if (pendingEdits[entry.segment.id] && entry.committedResult) {
        entry.suppressDragEvent = true;
        entry.renderer.setDirections(entry.committedResult);
        entry.suppressDragEvent = false;
      }
    });
    setPendingEdits({});
    setCommitError(null);
  }

  return (
    <section className="day" id="map" data-screen-label="Map" style={{paddingTop:'20px'}}>
      <div className="map-wrap" style={{margin:0}}>
        <div className="map-header">
          <div>
            <div className="mh-title">{routeStats.totalMilesText}</div>
            <div className="mh-sub" style={{marginTop:'3px'}}>
              SFO → LAX · May 23 – 29
              {routeStats.totalDurationText ? ` · ${routeStats.totalDurationText} driving` : ""}
            </div>
          </div>
          <span className="chip ocean">US-1</span>
        </div>

        {auth.user && (
          <div style={{
            margin: '10px 4px 4px',
            padding: '10px 12px',
            borderRadius: '12px',
            background: 'rgba(251,245,236,0.7)',
            border: '1px dashed rgba(53,49,42,0.2)',
            display: 'flex',
            alignItems: 'center',
            gap: '10px',
            flexWrap: 'wrap',
          }}>
            <span style={{
              fontFamily: 'var(--font-mono)',
              fontSize: '10px',
              letterSpacing: '0.08em',
              textTransform: 'uppercase',
              color: 'var(--ink-muted)',
              flex: '1 1 auto',
            }}>
              {hasPending
                ? `${pendingIds.length} segment${pendingIds.length === 1 ? '' : 's'} edited — review on map, then update`
                : 'Drag any segment to bend the route'}
            </span>
            <button
              className="link-btn map"
              onClick={commitPending}
              disabled={!hasPending || savingCommit}
              style={!hasPending ? { opacity: 0.5, cursor: 'not-allowed' } : null}
            >
              <span className="lb-ico">↺</span> {savingCommit ? 'Saving…' : 'Update segments'}
            </button>
            <button
              className="link-btn web"
              onClick={discardPending}
              disabled={!hasPending || savingCommit}
              style={!hasPending ? { opacity: 0.5, cursor: 'not-allowed' } : null}
            >
              <span className="lb-ico">⟲</span> Discard changes
            </button>
            {commitError && (
              <div style={{ flexBasis: '100%', fontSize: '11px', color: 'var(--terra-700)' }}>
                {commitError}
              </div>
            )}
          </div>
        )}

        {!auth.ready ? (
          <div id="route-map" ref={mapRef} />
        ) : hasKey ? (
          <>
            <div id="route-map" ref={mapRef} />
            {!loaded && !err && (
              <div style={{padding:'14px 12px 0', textAlign:'center', color:'var(--ink-muted)', fontFamily:'var(--font-mono)', fontSize:'11px'}}>
                Loading segment routes…
              </div>
            )}
            {err && (
              <div style={{padding:'14px 12px 0', textAlign:'center', color:'var(--terra-700)', fontSize:'13px'}}>
                {err}
              </div>
            )}
          </>
        ) : (
          <div className="map-placeholder">
            <div className="mp-title">Map key is not configured</div>
            <div className="mp-desc">
              Set <code>appConfig/public.gmapsKey</code> in Firestore for public map viewing. Signed-in users can also use <code>appConfig/client.gmapsKey</code>.
            </div>
          </div>
        )}
        <div className="map-legend">
          <span className="legend-pin">
            <span className="dot" style={{background: COLOR_OFFICIAL, boxShadow: `0 0 0 1px ${COLOR_OFFICIAL}`}} />
            Official route
          </span>
          {auth.user && (
            <span className="legend-pin">
              <span className="dot" style={{background: COLOR_PENDING, boxShadow: `0 0 0 1px ${COLOR_PENDING}`}} />
              Unsaved edit
            </span>
          )}
          <span className="legend-pin"><span className="dot" style={{background:'#23434a'}} />Stops · click for name</span>
        </div>
        {routeStats.legs.length > 0 && (
          <div style={{ marginTop: '10px', padding: '2px 4px 0' }}>
            {routeStats.legs.map((leg, idx) => (
              <div
                key={`${leg.from}-${leg.to}-${idx}`}
                style={{
                  display: 'flex',
                  alignItems: 'baseline',
                  justifyContent: 'space-between',
                  gap: '10px',
                  padding: '7px 0',
                  borderBottom: idx === routeStats.legs.length - 1 ? 'none' : '1px dashed rgba(53, 49, 42, 0.18)',
                  fontSize: '12px',
                  color: 'var(--ink-soft)',
                }}
              >
                <div style={{ minWidth: 0 }}>
                  <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--ink-900)', marginRight: '6px' }}>Day {leg.day}</span>
                  <span>{leg.from} → {leg.to}</span>
                </div>
                <div style={{ fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap' }}>
                  {leg.distanceText} · {leg.durationText}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </section>
  );
}

window.MapSection = MapSection;
