/* ============================================================
   MapView — Google Maps JS + real Solar API overlays.
   Replaces the prototype's Leaflet + Esri + canvas-heatmap path.
   ============================================================ */
/* global React */
const { useEffect: useEffectM, useRef: useRefM, useState: useStateM, useMemo: useMemoM } = React;

// ----- meters → degrees ---------------------------------------
function metersAt(lat) {
  return {
    dLat: 1 / 110540,
    dLng: 1 / (111320 * Math.cos(lat * Math.PI / 180)),
  };
}

// Compute the 4 corners of a panel given its center lat/lng, the segment azimuth (deg),
// the orientation, and the building's panel dimensions in meters.
function panelCorners({ center, azimuthDeg, orientation, panelWidthMeters, panelHeightMeters }) {
  const [clat, clng] = center;
  if (clat == null || clng == null) return null;
  const w = orientation === "PORTRAIT" ? panelWidthMeters : panelHeightMeters;
  const h = orientation === "PORTRAIT" ? panelHeightMeters : panelWidthMeters;
  // azimuth: 0° = north, clockwise. We want to rotate the rectangle so its long axis aligns with az.
  const a = (azimuthDeg || 0) * Math.PI / 180;
  const cos = Math.cos(a), sin = Math.sin(a);
  const { dLat, dLng } = metersAt(clat);
  const local = [
    [-w / 2, -h / 2],
    [ w / 2, -h / 2],
    [ w / 2,  h / 2],
    [-w / 2,  h / 2],
  ];
  return local.map(([lx, ly]) => {
    // x along east (right), y along north (up). Rotate by azimuth.
    const rx =  lx * cos + ly * sin;
    const ry = -lx * sin + ly * cos;
    return { lat: clat + ry * dLat, lng: clng + rx * dLng };
  });
}

// Convert a [lat, lng] array to a google.maps.LatLngLiteral.
function toLL(p) { return { lat: p[0], lng: p[1] }; }

// Tiny color ramp (used by enterprise marker coloring).
const FLUX_RAMP = [
  [26, 17, 71], [74, 29, 110], [138, 40, 118],
  [198, 61, 104], [238, 109, 61], [247, 167, 34], [253, 231, 37],
];
const SHADE_RAMP = FLUX_RAMP; // unused but kept for icons.jsx swatches reference
const DSM_RAMP = FLUX_RAMP;

function rampColor(stops, t) {
  t = Math.max(0, Math.min(1, t));
  const n = stops.length - 1;
  const i = Math.floor(t * n);
  const f = t * n - i;
  const a = stops[i], b = stops[Math.min(n, i + 1)];
  return [
    Math.round(a[0] + (b[0] - a[0]) * f),
    Math.round(a[1] + (b[1] - a[1]) * f),
    Math.round(a[2] + (b[2] - a[2]) * f),
  ];
}

// ----- Wait for the Google Maps JS API to load ----------------
let mapsReadyPromise = null;
function waitForMaps() {
  if (window.google && window.google.maps && window.google.maps.Map) {
    return Promise.resolve();
  }
  if (mapsReadyPromise) return mapsReadyPromise;
  mapsReadyPromise = new Promise(resolve => {
    const t = setInterval(() => {
      if (window.google && window.google.maps && window.google.maps.Map) {
        clearInterval(t);
        resolve();
      }
    }, 50);
  });
  return mapsReadyPromise;
}

const MAP_OPTS_BASE = {
  mapTypeId: "satellite",
  tilt: 0,
  disableDefaultUI: true,
  zoomControl: true,
  zoomControlOptions: { position: 0 },  // position will be remapped after google loads
  gestureHandling: "greedy",
  backgroundColor: "#1c1f24",
};

// ----- Layer fetch (resolves to { url, bounds }) --------------
async function fetchLayer(layer, lat, lng, monthIdx) {
  let url = `/api/layers/${layer}?lat=${lat}&lng=${lng}`;
  if (layer === "monthly_flux" && monthIdx != null) url += `&month=${monthIdx}`;
  const r = await fetch(url);
  if (!r.ok) throw new Error(`layer fetch failed: ${r.status}`);
  const j = await r.json();
  return j; // { png, bounds: {south,west,north,east} }
}

// ============================================================
//  HomeMapView
// ============================================================
function HomeMapView(props) {
  const {
    building, activeLayers, monthIdx, panelCount, hoveredSegment, onHoverSegment,
    pendingLocation, pendingPolygon, selectionMode, lastSelection,
  } = props;

  const containerRef = useRefM(null);
  const mapRef = useRefM(null);
  const overlaysRef = useRefM({});      // active GroundOverlay instances by layer key
  const layerCacheRef = useRefM({});    // layer key → { url, bounds }
  const vectorRef = useRefM({ segments: [], panels: [], footprint: null, hoverSeg: null });
  const interactiveRef = useRefM({
    pendingMarker: null,
    drawingPolyline: null,
    drawingVertices: [],
    drawingMarkers: [],
    pendingPolygonShape: null,
    drawnPolygonShape: null,
    mapClickListener: null,
    selectionMarker: null,
    selectionLink: null,
  });
  const [ready, setReady] = useStateM(false);
  // Always-current monthIdx for use inside async callbacks (so the preload
  // .then() picks the right month even if the user kept scrubbing).
  const monthIdxRef = useRefM(monthIdx);
  useEffectM(() => { monthIdxRef.current = monthIdx; }, [monthIdx]);

  // Init map
  useEffectM(() => {
    let cancelled = false;
    waitForMaps().then(() => {
      if (cancelled || mapRef.current) return;
      const center = building?.center || [28.4941, 77.0907];
      const opts = {
        ...MAP_OPTS_BASE,
        zoom: 20,
        center: { lat: center[0], lng: center[1] },
        mapId: (window.LEPTON_CFG?.mapId && window.LEPTON_CFG.mapId !== "__GOOGLE_MAPS_MAP_ID__") ? window.LEPTON_CFG.mapId : undefined,
        zoomControlOptions: { position: window.google.maps.ControlPosition.RIGHT_BOTTOM },
      };
      mapRef.current = new window.google.maps.Map(containerRef.current, opts);
      // Expose for debugging / e2e tests.
      window.__leptonHomeMap = mapRef.current;
      setReady(true);
    });
    return () => { cancelled = true; };
  }, []);

  // Listen for the search-bar 'pan-to' event. Just navigates the camera — no pin,
  // no fetch. User then clicks/draws to make a selection.
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const handler = (e) => {
      const { lat, lng, zoom } = e.detail || {};
      if (typeof lat !== "number" || typeof lng !== "number") return;
      map.panTo({ lat, lng });
      if (typeof zoom === "number") map.setZoom(zoom);
    };
    window.addEventListener("lepton:pan-to", handler);
    return () => window.removeEventListener("lepton:pan-to", handler);
  }, [ready]);

  // Recenter on building change. Hard setCenter + zoom reset (panTo is too gentle
  // for far-off jumps and keeps any user-zoomed level).
  useEffectM(() => {
    if (!ready || !building?.center) return;
    const map = mapRef.current;
    if (!map) return;
    map.setCenter({ lat: building.center[0], lng: building.center[1] });
    map.setZoom(20);
    // Wipe all raster overlays — they belong to the OLD building's GeoTIFF
    // bounds and would otherwise stick until the user toggles each layer.
    Object.values(overlaysRef.current).forEach(o => {
      if (o.overlay) o.overlay.setMap(null);
      if (o.overlays) o.overlays.forEach(ov => ov.setMap(null));
    });
    overlaysRef.current = {};
    // Also drop the layer fetch cache for the previous building.
    layerCacheRef.current = {};
  }, [ready, building?.id]);

  // ----- Raster layers via GroundOverlay -----------------------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const center = building.center;

    // Map of layer-name in our UI → backend layer key
    const RASTER_LAYERS = [
      { key: "rgb",         backend: "rgb",          enabled: !!activeLayers.rgb,         opacity: 1.0 },
      { key: "mask",        backend: "mask",         enabled: !!activeLayers.mask,        opacity: 0.50 },
      { key: "annualFlux",  backend: "annual_flux",  enabled: !!activeLayers.annualFlux,  opacity: 0.78 },
      { key: "monthlyFlux", backend: "monthly_flux", enabled: !!activeLayers.monthlyFlux, opacity: 0.82, refreshOnMonth: true },
      { key: "shaded",      backend: "shaded_hours", enabled: !!activeLayers.shaded,      opacity: 0.72 },
      { key: "dsm",         backend: "dsm",          enabled: !!activeLayers.dsm,         opacity: 0.78 },
    ];

    RASTER_LAYERS.forEach(L => {
      // ----- Monthly-flux special path: keep 12 pre-loaded overlay instances
      // so temporal playback is just a setMap() toggle (~instant). The actual
      // month switching lives in a separate useEffect below. -----
      if (L.key === "monthlyFlux") {
        const slot = overlaysRef.current[L.key];
        const slotKey = `${building.id}/monthlyFlux-all`;

        if (!L.enabled) {
          if (slot && slot.overlays) slot.overlays.forEach(o => o.setMap(null));
          if (slot) delete overlaysRef.current[L.key];
          return;
        }
        // If preload is already done or in flight for this building → noop here.
        // The visibility-toggle effect handles which month is shown.
        if (slot && slot.cacheKey === slotKey) return;

        // Start parallel preload of all 12 months.
        overlaysRef.current[L.key] = { cacheKey: slotKey, overlays: null };
        Promise.all(Array.from({ length: 12 }, async (_, m) => {
          const k = `${building.id}/monthlyFlux-m${m}`;
          if (layerCacheRef.current[k]) return layerCacheRef.current[k];
          const j = await fetchLayer("monthly_flux", center[0], center[1], m);
          layerCacheRef.current[k] = j;
          return j;
        })).then(results => {
          const cur = overlaysRef.current[L.key];
          if (!cur || cur.cacheKey !== slotKey) return;  // got swapped during load
          // Warm the browser's HTTP cache for all 12 PNGs up front.
          // GroundOverlay only fetches its image when first attached to a map,
          // so without this, scrubbing through months for the first time still
          // shows a brief flash per month. Preloading <img>s in parallel removes
          // that latency entirely.
          results.forEach(j => { const im = new Image(); im.src = j.png; });
          const overlays = results.map(j => {
            const b = j.bounds;
            const bounds = new g.LatLngBounds(
              { lat: b.south, lng: b.west },
              { lat: b.north, lng: b.east },
            );
            return new g.GroundOverlay(j.png, bounds, { clickable: false, opacity: L.opacity });
          });
          overlaysRef.current[L.key] = { cacheKey: slotKey, overlays };
          const m = monthIdxRef.current;
          overlays.forEach((o, i) => o.setMap(i === m ? map : null));
        }).catch(err => console.warn("monthly flux preload failed:", err.message));
        return;
      }

      // ----- Default path for non-temporal layers -----
      const cacheKey = `${building.id}/${L.key}`;
      const active = overlaysRef.current[L.key];
      if (!L.enabled) {
        if (active) { active.overlay.setMap(null); delete overlaysRef.current[L.key]; }
        return;
      }
      if (active && active.cacheKey === cacheKey) return;
      if (active) { active.overlay.setMap(null); delete overlaysRef.current[L.key]; }

      let cancelled = false;
      const promise = layerCacheRef.current[cacheKey]
        ? Promise.resolve(layerCacheRef.current[cacheKey])
        : fetchLayer(L.backend, center[0], center[1])
            .then(j => { layerCacheRef.current[cacheKey] = j; return j; });
      promise.then(j => {
        if (cancelled || !overlaysRef.current || overlaysRef.current[L.key]?.cacheKey === cacheKey) return;
        const b = j.bounds;
        const bounds = new g.LatLngBounds(
          { lat: b.south, lng: b.west },
          { lat: b.north, lng: b.east },
        );
        const ov = new g.GroundOverlay(j.png, bounds, { clickable: false, opacity: L.opacity });
        ov.setMap(map);
        overlaysRef.current[L.key] = { overlay: ov, cacheKey };
      }).catch(err => console.warn(`Layer ${L.key} failed:`, err.message));
    });
  }, [ready, building.id, activeLayers.rgb, activeLayers.mask, activeLayers.annualFlux, activeLayers.monthlyFlux, activeLayers.shaded, activeLayers.dsm, monthIdx]);

  // ----- Monthly-flux visibility toggle (instant — just setMap on overlays). -----
  useEffectM(() => {
    if (!ready) return;
    if (!activeLayers.monthlyFlux) return;
    const map = mapRef.current;
    const slot = overlaysRef.current.monthlyFlux;
    if (!slot || !slot.overlays) return;  // preload not done yet — .then() will handle it
    slot.overlays.forEach((o, m) => o.setMap(m === monthIdx ? map : null));
  }, [ready, monthIdx, activeLayers.monthlyFlux]);

  // ----- Footprint + roof segments + panels (vector) -----------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;

    // Clear previous vector layers
    vectorRef.current.segments.forEach(s => s.setMap(null));
    vectorRef.current.panels.forEach(p => p.setMap(null));
    if (vectorRef.current.footprint) vectorRef.current.footprint.setMap(null);
    if (vectorRef.current.hoverSeg) vectorRef.current.hoverSeg.setMap(null);
    vectorRef.current = { segments: [], panels: [], footprint: null, hoverSeg: null };

    // Footprint (boundingBox rectangle)
    if (building.footprint && building.footprint.length) {
      const path = building.footprint.map(toLL);
      const fp = new g.Polygon({
        paths: path,
        strokeColor: "#ffffff",
        strokeOpacity: 0.55,
        strokeWeight: 1.0,
        fillOpacity: 0,
        clickable: false,
        map,
      });
      vectorRef.current.footprint = fp;
    }

    // Roof segments
    if (activeLayers.segments) {
      const segColors = ["#f59e0b", "#3b82f6", "#10b981", "#ec4899", "#a855f7", "#ef4444"];
      (building.roofSegments || []).forEach((seg, idx) => {
        if (!seg.polygon || seg.polygon.length < 3) return;
        const poly = new g.Polygon({
          paths: seg.polygon.map(toLL),
          strokeColor: segColors[idx % segColors.length],
          strokeOpacity: 0.95,
          strokeWeight: 1.8,
          fillOpacity: 0,
          // Don't swallow map clicks — pin-drop / polygon vertex placement must work
          // even when the cursor is over a roof segment outline.
          clickable: false,
          map,
        });
        vectorRef.current.segments.push(poly);
      });
    }

    // Panels: top-N by yearlyEnergyDcKwh
    if (activeLayers.panels && building.panels && building.panels.length) {
      const sorted = [...building.panels].sort((a, b) => (b.yearlyEnergyDcKwh || 0) - (a.yearlyEnergyDcKwh || 0));
      const visible = sorted.slice(0, panelCount);
      visible.forEach(p => {
        const seg = building.roofSegments[p.segIdx] || building.roofSegments[0];
        const corners = panelCorners({
          center: p.center,
          azimuthDeg: seg ? seg.azimuthDeg : 0,
          orientation: p.orientation,
          panelWidthMeters: building.panelWidthMeters,
          panelHeightMeters: building.panelHeightMeters,
        });
        if (!corners) return;
        const poly = new g.Polygon({
          paths: corners,
          strokeColor: "#0d2c54",
          strokeWeight: 0.6,
          fillColor: "#1e40af",
          fillOpacity: 0.75,
          clickable: false,
          map,
        });
        vectorRef.current.panels.push(poly);
      });
    }

    // Hovered segment highlight
    if (hoveredSegment) {
      const seg = (building.roofSegments || []).find(s => s.id === hoveredSegment);
      if (seg && seg.polygon && seg.polygon.length >= 3) {
        vectorRef.current.hoverSeg = new g.Polygon({
          paths: seg.polygon.map(toLL),
          strokeColor: "#ffffff",
          strokeOpacity: 0.95,
          strokeWeight: 2.5,
          fillColor: "#ffffff",
          fillOpacity: 0.18,
          clickable: false,
          map,
        });
      }
    }
  }, [ready, building.id, activeLayers.segments, activeLayers.panels, panelCount, hoveredSegment]);

  // ----- Pending marker (draggable) -----------------------------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const ir = interactiveRef.current;

    // Remove any existing pending marker.
    if (ir.pendingMarker) {
      ir.pendingMarker.map = null;
      ir.pendingMarker = null;
    }
    if (!pendingLocation) return;

    // Build a small pin-shaped element.
    const el = document.createElement("div");
    el.style.cssText = `
      width: 20px; height: 20px; border-radius: 50%;
      background: var(--accent-strong, #d46a14);
      border: 3px solid white;
      box-shadow: 0 2px 8px rgba(0,0,0,0.25), 0 0 0 1px var(--ink-1, #000);
      cursor: grab;
      transition: transform 0.1s;
    `;
    el.title = "Drag to refine, then Compute solar potential";

    const marker = new g.marker.AdvancedMarkerElement({
      position: { lat: pendingLocation.lat, lng: pendingLocation.lng },
      map,
      content: el,
      gmpDraggable: true,
    });
    marker.addListener("dragend", (e) => {
      const lat = e.latLng?.lat ?? marker.position?.lat;
      const lng = e.latLng?.lng ?? marker.position?.lng;
      if (typeof lat === "number" && typeof lng === "number") {
        window.LEPTON_LOAD.setPending({
          lat, lng,
          label: pendingLocation.source === "polygon" ? "Drawn polygon" : null,
          source: pendingLocation.source === "polygon" ? "polygon" : "pin",
        });
      }
    });
    ir.pendingMarker = marker;
  }, [ready, pendingLocation?.lat, pendingLocation?.lng]);

  // ----- "Your click" marker after Compute (when offset > threshold) -----
  // Helps the user see that the API resolved a building NEAR their click rather
  // than AT it. Renders only when offset > 15 m; otherwise it'd just clutter.
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const ir = interactiveRef.current;

    if (ir.selectionMarker) { ir.selectionMarker.map = null; ir.selectionMarker = null; }
    if (ir.selectionLink) { ir.selectionLink.setMap(null); ir.selectionLink = null; }
    if (!lastSelection || lastSelection.offsetMeters < 15) return;
    if (!building?.center) return;

    // Small ringed dot at the user's original click.
    const el = document.createElement("div");
    el.style.cssText = `
      width: 14px; height: 14px; border-radius: 50%;
      background: rgba(255,255,255,0.95); border: 2px solid #ef4444;
      box-shadow: 0 0 0 1.5px rgba(0,0,0,0.4);
    `;
    el.title = `Your click (${lastSelection.offsetMeters.toFixed(0)} m from resolved building)`;
    ir.selectionMarker = new g.marker.AdvancedMarkerElement({
      position: { lat: lastSelection.lat, lng: lastSelection.lng },
      map,
      content: el,
    });

    // Dashed line from click → resolved building center, so the offset is visible.
    ir.selectionLink = new g.Polyline({
      path: [
        { lat: lastSelection.lat, lng: lastSelection.lng },
        { lat: building.center[0], lng: building.center[1] },
      ],
      strokeColor: "#ef4444",
      strokeOpacity: 0,
      icons: [{
        icon: { path: "M 0,-1 0,1", strokeOpacity: 0.7, scale: 2 },
        offset: "0",
        repeat: "8px",
      }],
      clickable: false,
      map,
    });
  }, [ready, lastSelection?.lat, lastSelection?.lng, lastSelection?.offsetMeters, building?.id]);

  // ----- Click-to-drop pin (when in 'pin' selection mode) -------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const ir = interactiveRef.current;

    if (ir.mapClickListener) {
      g.event.removeListener(ir.mapClickListener);
      ir.mapClickListener = null;
    }
    // Map clicks ALWAYS handle either pin drop or polygon vertex add.
    // Pin is the default; polygon is the explicit toggle.
    ir.mapClickListener = map.addListener("click", (e) => {
      const lat = e.latLng?.lat();
      const lng = e.latLng?.lng();
      if (typeof lat !== "number" || typeof lng !== "number") return;
      if (selectionMode !== "polygon") {
        // Default: drop a pin.
        window.LEPTON_LOAD.setPending({ lat, lng, label: null, source: "pin" });
      } else {
        // Append to drawing vertices.
        const vs = ir.drawingVertices.concat({ lat, lng });
        ir.drawingVertices = vs;
        renderPolygonDraft();
        // Close on click near first vertex (within ~4 m at this zoom)
        if (vs.length >= 3) {
          const first = vs[0];
          const d = Math.hypot((first.lat - lat) * 110540, (first.lng - lng) * 95000);
          if (d < 6) {
            // Close polygon
            window.LEPTON_LOAD.setPolygon(vs.slice(0, -1)); // drop the duplicate "close" click
            ir.drawingVertices = [];
            clearPolygonDraft();
            return;
          }
        }
      }
    });

    function clearPolygonDraft() {
      if (ir.drawingPolyline) { ir.drawingPolyline.setMap(null); ir.drawingPolyline = null; }
      ir.drawingMarkers.forEach(m => { m.map = null; });
      ir.drawingMarkers = [];
    }

    function renderPolygonDraft() {
      // Polyline through current vertices
      if (ir.drawingPolyline) ir.drawingPolyline.setMap(null);
      ir.drawingMarkers.forEach(m => { m.map = null; });
      ir.drawingMarkers = [];

      ir.drawingPolyline = new g.Polyline({
        path: ir.drawingVertices.map(v => ({ lat: v.lat, lng: v.lng })),
        strokeColor: "#f59e0b",
        strokeOpacity: 0.95,
        strokeWeight: 2,
        map,
      });
      ir.drawingVertices.forEach((v, i) => {
        const el = document.createElement("div");
        el.style.cssText = `
          width: 9px; height: 9px; border-radius: 50%;
          background: white; border: 2px solid #f59e0b;
          ${i === 0 ? "box-shadow: 0 0 0 2px rgba(245,158,11,0.4);" : ""}
        `;
        const m = new g.marker.AdvancedMarkerElement({
          position: { lat: v.lat, lng: v.lng }, map, content: el,
        });
        ir.drawingMarkers.push(m);
      });
    }

    return () => {
      if (ir.mapClickListener) {
        g.event.removeListener(ir.mapClickListener);
        ir.mapClickListener = null;
      }
    };
  }, [ready, selectionMode]);

  // Clear in-progress polygon when leaving polygon mode.
  useEffectM(() => {
    const ir = interactiveRef.current;
    if (selectionMode !== "polygon" && ir.drawingVertices.length) {
      ir.drawingVertices = [];
      if (ir.drawingPolyline) { ir.drawingPolyline.setMap(null); ir.drawingPolyline = null; }
      ir.drawingMarkers.forEach(m => { m.map = null; });
      ir.drawingMarkers = [];
    }
  }, [selectionMode]);

  // ----- Render the completed pendingPolygon as a filled shape ---
  useEffectM(() => {
    if (!ready) return;
    const g = window.google.maps;
    const map = mapRef.current;
    const ir = interactiveRef.current;
    if (ir.pendingPolygonShape) { ir.pendingPolygonShape.setMap(null); ir.pendingPolygonShape = null; }
    if (!pendingPolygon || pendingPolygon.length < 3) return;

    ir.pendingPolygonShape = new g.Polygon({
      paths: pendingPolygon.map(p => ({ lat: p.lat, lng: p.lng })),
      strokeColor: "#f59e0b",
      strokeOpacity: 1.0,
      strokeWeight: 2,
      fillColor: "#f59e0b",
      fillOpacity: 0.12,
      clickable: false,
      map,
    });
  }, [ready, pendingPolygon]);

  // ----- Render user's confirmed polygon (after Compute) ---------
  useEffectM(() => {
    if (!ready) return;
    const g = window.google.maps;
    const map = mapRef.current;
    const ir = interactiveRef.current;
    if (ir.drawnPolygonShape) { ir.drawnPolygonShape.setMap(null); ir.drawnPolygonShape = null; }
    if (!building.userFootprint || building.userFootprint.length < 3) return;

    ir.drawnPolygonShape = new g.Polygon({
      paths: building.userFootprint.map(p => ({ lat: p[0], lng: p[1] })),
      strokeColor: "#10b981",
      strokeOpacity: 0.9,
      strokeWeight: 2,
      fillColor: "#10b981",
      fillOpacity: 0.08,
      clickable: false,
      map,
    });
  }, [ready, building.id, building.userFootprint]);

  // Cleanup overlays + vectors + interactive on unmount
  useEffectM(() => () => {
    Object.values(overlaysRef.current || {}).forEach(o => {
      if (o.overlay) o.overlay.setMap(null);
      if (o.overlays) o.overlays.forEach(ov => ov.setMap(null));  // monthly-flux: 12-overlay slot
    });
    vectorRef.current.segments.forEach(s => s.setMap(null));
    vectorRef.current.panels.forEach(p => p.setMap(null));
    if (vectorRef.current.footprint) vectorRef.current.footprint.setMap(null);
    if (vectorRef.current.hoverSeg) vectorRef.current.hoverSeg.setMap(null);
    const ir = interactiveRef.current;
    if (ir.pendingMarker) ir.pendingMarker.map = null;
    if (ir.drawingPolyline) ir.drawingPolyline.setMap(null);
    ir.drawingMarkers.forEach(m => { m.map = null; });
    if (ir.pendingPolygonShape) ir.pendingPolygonShape.setMap(null);
    if (ir.drawnPolygonShape) ir.drawnPolygonShape.setMap(null);
    if (ir.selectionMarker) ir.selectionMarker.map = null;
    if (ir.selectionLink) ir.selectionLink.setMap(null);
  }, []);

  return <div ref={containerRef} style={{ position: "absolute", inset: 0, background: "#1c1f24" }} />;
}

// ============================================================
//  EnterpriseMapView
// ============================================================
function EnterpriseMapView({ scenario, colorMode, selectedId, onSelect, hoveredId, onHover }) {
  const containerRef = useRefM(null);
  const mapRef = useRefM(null);
  const markersRef = useRefM([]);
  const [ready, setReady] = useStateM(false);

  useEffectM(() => {
    let cancelled = false;
    waitForMaps().then(() => {
      if (cancelled || mapRef.current) return;
      const opts = {
        ...MAP_OPTS_BASE,
        zoom: scenario.zoom,
        center: { lat: scenario.center[0], lng: scenario.center[1] },
        mapId: (window.LEPTON_CFG?.mapId && window.LEPTON_CFG.mapId !== "__GOOGLE_MAPS_MAP_ID__") ? window.LEPTON_CFG.mapId : undefined,
        zoomControlOptions: { position: window.google.maps.ControlPosition.RIGHT_BOTTOM },
      };
      mapRef.current = new window.google.maps.Map(containerRef.current, opts);
      setReady(true);
    });
    return () => { cancelled = true; };
  }, []);

  // Re-center when scenario changes
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    if (!map) return;
    map.setCenter({ lat: scenario.center[0], lng: scenario.center[1] });
    map.setZoom(scenario.zoom);
  }, [ready, scenario.id]);

  // Markers
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    markersRef.current.forEach(m => { m.setMap && m.setMap(null); if (m.map) m.map = null; });
    markersRef.current = [];
    if (!scenario.buildings || !scenario.buildings.length) return;

    const values = scenario.buildings.map(b => {
      if (colorMode === "kwp") return b.kWp || 0;
      if (colorMode === "payback") return -(b.payback || 99);
      return b.suitability || 0;
    });
    const vmin = Math.min(...values), vmax = Math.max(...values);
    const norm = v => (v - vmin) / ((vmax - vmin) || 1);

    scenario.buildings.forEach(b => {
      const v = colorMode === "kwp" ? (b.kWp || 0)
              : colorMode === "payback" ? -(b.payback || 99)
              : (b.suitability || 0);
      const t = norm(v);
      const rgb = rampColor(FLUX_RAMP, t);
      const isSel = b.id === selectedId;
      const isHov = b.id === hoveredId;
      const size = isSel ? 18 : isHov ? 16 : 12;

      const el = document.createElement("div");
      el.className = `bldg-marker${isSel ? " is-selected" : ""}`;
      el.style.background = `rgb(${rgb.join(",")})`;
      el.style.width = el.style.height = `${size}px`;
      el.title = `${b.id} · ${b.clusterName}\n${b.kWp} kWp · ${(b.yearlyKwh/1000).toFixed(1)} MWh/yr · payback ${b.payback} yrs`;

      const marker = new g.marker.AdvancedMarkerElement({
        position: { lat: b.lat, lng: b.lng },
        map,
        content: el,
      });
      el.addEventListener("click", () => onSelect && onSelect(b.id));
      el.addEventListener("mouseenter", () => onHover && onHover(b.id));
      el.addEventListener("mouseleave", () => onHover && onHover(null));
      markersRef.current.push(marker);
    });
  }, [ready, scenario.id, scenario.buildings, colorMode, selectedId, hoveredId]);

  useEffectM(() => () => {
    markersRef.current.forEach(m => { if (m.map) m.map = null; });
    markersRef.current = [];
  }, []);

  return <div ref={containerRef} style={{ position: "absolute", inset: 0, background: "#1c1f24" }} />;
}

// ============================================================
//  CityMapView — heatmap + polygon for the city-analysis tab.
//  Uses the same pendingPolygon / selectionMode flow as the
//  homeowner map (driven by window.LEPTON_LOAD.setPolygon /
//  setSelectionMode), so the PolygonToggle pill and the draw
//  experience are identical across tabs.
// ============================================================
function CityMapView(props) {
  const { pendingPolygon, selectionMode, heatmapUrl, heatmapBounds, autoFitOnPolygon } = props;

  const containerRef = useRefM(null);
  const mapRef = useRefM(null);
  const overlayRef = useRefM(null);        // GroundOverlay for heatmap
  const drawingRef = useRefM({
    vertices: [],                          // in-progress vertices (before close)
    polyline: null,
    markers: [],
    polygonShape: null,                    // closed polygon shape
    clickListener: null,
  });
  // Track the polygon prop value via ref so the click listener (bound once per
  // selectionMode change) sees the latest closed polygon.
  const polygonRef = useRefM(pendingPolygon);
  useEffectM(() => { polygonRef.current = pendingPolygon; }, [pendingPolygon]);
  const [ready, setReady] = useStateM(false);

  // Init map (default Delhi NCR; pan/search elsewhere as needed).
  useEffectM(() => {
    let cancelled = false;
    waitForMaps().then(() => {
      if (cancelled || mapRef.current) return;
      const opts = {
        ...MAP_OPTS_BASE,
        zoom: 13,
        center: { lat: 28.5275, lng: 77.0732 },
        mapId: (window.LEPTON_CFG?.mapId && window.LEPTON_CFG.mapId !== "__GOOGLE_MAPS_MAP_ID__") ? window.LEPTON_CFG.mapId : undefined,
        zoomControlOptions: { position: window.google.maps.ControlPosition.RIGHT_BOTTOM },
      };
      mapRef.current = new window.google.maps.Map(containerRef.current, opts);
      window.__leptonCityMap = mapRef.current;
      setReady(true);
    });
    return () => { cancelled = true; };
  }, []);

  // Listen for search-bar 'pan-to' events (same as HomeMapView), so the existing
  // autocomplete in homeowner mode also works to navigate the city map.
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const handler = (e) => {
      const { lat, lng, zoom } = e.detail || {};
      if (typeof lat !== "number" || typeof lng !== "number") return;
      map.panTo({ lat, lng });
      if (typeof zoom === "number") map.setZoom(zoom);
    };
    window.addEventListener("lepton:pan-to", handler);
    return () => window.removeEventListener("lepton:pan-to", handler);
  }, [ready]);

  // ----- Polygon drawing: same flow as HomeMapView ----------------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const d = drawingRef.current;

    function clearDraft() {
      if (d.polyline) { d.polyline.setMap(null); d.polyline = null; }
      d.markers.forEach(m => { m.map = null; });
      d.markers = [];
    }

    function renderDraft() {
      if (d.polyline) d.polyline.setMap(null);
      d.markers.forEach(m => { m.map = null; });
      d.markers = [];

      d.polyline = new g.Polyline({
        path: d.vertices.map(v => ({ lat: v.lat, lng: v.lng })),
        strokeColor: "#f59e0b",
        strokeOpacity: 0.95,
        strokeWeight: 2,
        map,
      });
      d.vertices.forEach((v, i) => {
        const el = document.createElement("div");
        el.style.cssText = `
          width: 10px; height: 10px; border-radius: 50%;
          background: white; border: 2px solid #f59e0b;
          ${i === 0 ? "box-shadow: 0 0 0 3px rgba(245,158,11,0.35);" : ""}
        `;
        const m = new g.marker.AdvancedMarkerElement({
          position: { lat: v.lat, lng: v.lng }, map, content: el,
        });
        d.markers.push(m);
      });
    }

    if (d.clickListener) { g.event.removeListener(d.clickListener); d.clickListener = null; }
    if (selectionMode !== "polygon") return;

    d.clickListener = map.addListener("click", (e) => {
      const lat = e.latLng?.lat();
      const lng = e.latLng?.lng();
      if (typeof lat !== "number" || typeof lng !== "number") return;

      // Drawing while a closed polygon is shown: starting a fresh draft, so
      // clear the existing polygon via the global setter.
      if (polygonRef.current && polygonRef.current.length >= 3 && d.vertices.length === 0) {
        window.LEPTON_LOAD.setPolygon(null);
      }

      const vs = d.vertices.concat({ lat, lng });
      d.vertices = vs;
      renderDraft();

      if (vs.length >= 4) {
        const first = vs[0];
        const dLat = (first.lat - lat) * 110540;
        const dLng = (first.lng - lng) * 95000;
        const dist = Math.sqrt(dLat * dLat + dLng * dLng);
        if (dist < 18) {
          const closed = vs.slice(0, -1);
          d.vertices = [];
          clearDraft();
          // Same global flow homeowner uses — store hooks all other
          // listeners (e.g. pendingLocation centroid) too.
          window.LEPTON_LOAD.setPolygon(closed);
        }
      }
    });

    return () => {
      if (d.clickListener) { g.event.removeListener(d.clickListener); d.clickListener = null; }
    };
  }, [ready, selectionMode]);

  // ----- Render closed polygon -----------------------------------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;
    const d = drawingRef.current;

    if (d.polygonShape) { d.polygonShape.setMap(null); d.polygonShape = null; }
    if (!pendingPolygon || pendingPolygon.length < 3) return;

    d.polygonShape = new g.Polygon({
      paths: pendingPolygon.map(p => ({ lat: p.lat, lng: p.lng })),
      strokeColor: "#f59e0b",
      strokeOpacity: 0.95,
      strokeWeight: 2,
      fillColor: "#f59e0b",
      fillOpacity: 0.10,
      clickable: false,
      map,
    });

    if (autoFitOnPolygon) {
      const bounds = new g.LatLngBounds();
      pendingPolygon.forEach(p => bounds.extend({ lat: p.lat, lng: p.lng }));
      map.fitBounds(bounds, { top: 80, right: 80, bottom: 80, left: 80 });
    }
  }, [ready, pendingPolygon]);

  // Clear draft when leaving polygon mode.
  useEffectM(() => {
    if (selectionMode !== "polygon") {
      const d = drawingRef.current;
      d.vertices = [];
      if (d.polyline) { d.polyline.setMap(null); d.polyline = null; }
      d.markers.forEach(m => { m.map = null; });
      d.markers = [];
    }
  }, [selectionMode]);

  // ----- Heatmap GroundOverlay ----------------------------------
  useEffectM(() => {
    if (!ready) return;
    const map = mapRef.current;
    const g = window.google.maps;

    if (overlayRef.current) {
      overlayRef.current.setMap(null);
      overlayRef.current = null;
    }
    if (!heatmapUrl || !heatmapBounds) return;

    const b = heatmapBounds;
    const bounds = new g.LatLngBounds(
      { lat: b.south, lng: b.west },
      { lat: b.north, lng: b.east },
    );
    const ov = new g.GroundOverlay(heatmapUrl, bounds, { clickable: false, opacity: 0.82 });
    ov.setMap(map);
    overlayRef.current = ov;
  }, [ready, heatmapUrl, heatmapBounds?.south, heatmapBounds?.west, heatmapBounds?.north, heatmapBounds?.east]);

  // Cleanup on unmount.
  useEffectM(() => () => {
    if (overlayRef.current) overlayRef.current.setMap(null);
    const d = drawingRef.current;
    if (d.polyline) d.polyline.setMap(null);
    d.markers.forEach(m => { m.map = null; });
    if (d.polygonShape) d.polygonShape.setMap(null);
  }, []);

  return <div ref={containerRef} style={{ position: "absolute", inset: 0, background: "#1c1f24" }} />;
}

Object.assign(window, { HomeMapView, EnterpriseMapView, CityMapView, FLUX_RAMP, SHADE_RAMP, DSM_RAMP });
