#!/usr/bin/env bash
set -euo pipefail

# Run from frontend repo root
# (the folder that contains src/)
test -d "src" || { echo "❌ Run this from the FRONTEND repo root (where ./src exists)"; exit 1; }

TS="$(date +%Y%m%d-%H%M%S)"
echo "✅ Patching SocioWire frontend (timestamp: $TS)"

# backup
for f in \
  src/components/Map/useMapCore.js \
  src/components/Map/usePostsEngine.js
do
  if [ -f "$f" ]; then
    cp -a "$f" "$f.bak.$TS"
    echo "📦 Backup: $f -> $f.bak.$TS"
  else
    echo "⚠️ Missing file: $f (will be created)"
  fi
done

# -----------------------------
# FILE: src/components/Map/useMapCore.js
# -----------------------------
mkdir -p src/components/Map
cat > src/components/Map/useMapCore.js <<'EOF'
import { useEffect, useRef, useState } from "react";
import maplibregl from "maplibre-gl";
import { LAST_VIEW_KEY } from "./mapConfig";
import { getViewFromMap } from "./mapGeo";
import { clearAllMarkers, applyOcclusionForExpanded } from "./markerManager";

// MapLibre base style by theme
function getBaseStyle(theme) {
  const t = theme || "dark";
  let tiles;

  switch (t) {
    case "light":
      tiles = ["https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"];
      break;
    case "blue":
      tiles = ["https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png"];
      break;
    case "dark":
    default:
      tiles = ["https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"];
      break;
  }

  return {
    version: 8,
    sources: {
      "carto-base": {
        type: "raster",
        tiles,
        tileSize: 256,
        attribution: "© OpenStreetMap contributors © CARTO",
      },
    },
    layers: [
      {
        id: "carto-base-layer",
        type: "raster",
        source: "carto-base",
      },
    ],
  };
}

export function useMapCore(theme) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);

  const markersRef = useRef([]);
  const expandedElRef = useRef(null);

  // default large radius
  const [viewParams, setViewParams] = useState({
    center: null,
    radiusKm: 750,
  });

  const [hasLastView, setHasLastView] = useState(false);

  function closeExpandedIfAny() {
    const el = expandedElRef.current;
    if (el && el.__renderCompact) el.__renderCompact();
    expandedElRef.current = null;
  }

  function safeUpdateViewParams(map) {
    try {
      const vp = getViewFromMap(map);
      if (vp && Array.isArray(vp.center) && vp.center.length === 2) {
        setViewParams(vp);
      }
    } catch {
      // ignore
    }
  }

  useEffect(() => {
    if (!containerRef.current) return;

    const map = new maplibregl.Map({
      container: containerRef.current,
      style: getBaseStyle(theme || "dark"),
      center: [-95, 40],
      zoom: 3.5,
      pitch: 0,
      bearing: 0,
      antialias: true,
    });

    // Restore last view if available
    let hadLastView = false;
    try {
      const raw = localStorage.getItem(LAST_VIEW_KEY);
      if (raw) {
        const v = JSON.parse(raw);
        if (
          typeof v.lat === "number" &&
          typeof v.lon === "number" &&
          typeof v.zoom === "number"
        ) {
          map.setCenter([v.lon, v.lat]);
          map.setZoom(v.zoom);
          hadLastView = true;
        }
      }
    } catch {}
    setHasLastView(hadLastView);

    map.addControl(new maplibregl.NavigationControl(), "top-right");
    mapRef.current = map;

    // Re-occlude markers when post expanded
    const reOcclude = () => {
      const el = expandedElRef.current;
      if (el) applyOcclusionForExpanded(markersRef, el);
    };
    map.on("move", reOcclude);
    map.on("zoom", reOcclude);

    // Close expanded when clicking/dragging map
    map.on("click", () => closeExpandedIfAny());
    map.on("dragstart", () => closeExpandedIfAny());

    // ✅ CRITICAL FIX:
    // Set view params ASAP and also on load/idle/moveend.
    // On some devices/prod, relying on 'load' only can miss initial fetch triggers.
    const update = () => safeUpdateViewParams(map);

    map.on("load", update);
    map.on("idle", update);
    map.on("moveend", () => {
      update();
      try {
        const vp = getViewFromMap(map);
        const center = vp.center;
        localStorage.setItem(
          LAST_VIEW_KEY,
          JSON.stringify({
            lat: center[1],
            lon: center[0],
            zoom: map.getZoom(),
          })
        );
      } catch {}
    });

    // Also do an early update next frame (helps first load)
    requestAnimationFrame(update);

    return () => {
      clearAllMarkers(markersRef, expandedElRef);
      map.remove();
      mapRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Theme style switch
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    // setStyle triggers style reload; we still want viewParams updated after
    map.setStyle(getBaseStyle(theme || "dark"));

    // update view params after style swap (next frame)
    requestAnimationFrame(() => {
      try {
        safeUpdateViewParams(map);
      } catch {}
    });
  }, [theme]);

  return {
    containerRef,
    mapRef,
    markersRef,
    expandedElRef,
    viewParams,
    hasLastView,
  };
}
EOF

# -----------------------------
# FILE: src/components/Map/usePostsEngine.js
# -----------------------------
cat > src/components/Map/usePostsEngine.js <<'EOF'
import { useCallback, useEffect, useRef, useState } from "react";
import { fetchPosts } from "../../api/client"; // keep existing endpoint for now
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
import { haversineKm } from "./mapGeo";
import { createMarkerForPost, clearAllMarkers } from "./markerManager";

/**
 * Posts engine:
 * - keeps a cache (allPostsRef)
 * - applies filters (cat/sub/time) for map + Sociowall
 * - fetches by zone
 *
 * ✅ FIX: first page load must fetch even if viewParams.center is null.
 * We fallback to map.getCenter() and default radius 750.
 */
export function usePostsEngine({
  mapRef,
  viewParams,
  mainFilter,
  subFilter,
  timeFilter,
  markersRef,
  expandedElRef,
}) {
  const allPostsRef = useRef([]);

  const lastFetchRef = useRef({
    center: null,
    radiusKm: null,
    filterKey: "",
  });

  const [status, setStatus] = useState("Loading posts...");
  const [visiblePosts, setVisiblePosts] = useState([]);
  const [loadingPosts, setLoadingPosts] = useState(false);
  const [loadError, setLoadError] = useState("");

  const rebuildMarkersForFilters = useCallback(
    (tf) => {
      const map = mapRef.current;
      if (!map) return;

      clearAllMarkers(markersRef, expandedElRef);

      const posts = allPostsRef.current || [];
      const catCode = categoryCode(mainFilter);

      const visible = posts.filter((p) => {
        if (catCode) {
          const pc = (p.category || p.Category || "").toString().toUpperCase();
          if (pc !== catCode) return false;
        }
        if (!matchesSubFilter(p, subFilter)) return false;
        if (!matchesTimeFilter(p.created_at || p.CreatedAt, tf)) return false;
        return true;
      });

      visible.forEach((post) => {
        createMarkerForPost(post, mapRef, markersRef, expandedElRef);
      });

      setVisiblePosts(visible);
      setStatus(visible.length ? "" : "No posts found.");
    },
    [mapRef, markersRef, expandedElRef, mainFilter, subFilter]
  );

  useEffect(() => {
    rebuildMarkersForFilters(timeFilter);
  }, [timeFilter, mainFilter, subFilter, rebuildMarkersForFilters]);

  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    let cancelled = false;

    async function load() {
      // ✅ fallback center/radius if viewParams not ready yet
      const centerArr =
        (viewParams && Array.isArray(viewParams.center) && viewParams.center.length === 2)
          ? viewParams.center
          : (() => {
              const c = map.getCenter();
              return [c.lng, c.lat];
            })();

      const radiusKm =
        (viewParams && typeof viewParams.radiusKm === "number" && viewParams.radiusKm > 0)
          ? viewParams.radiusKm
          : 750;

      const [lng, lat] = centerArr;

      const filterKey = `${mainFilter}|${subFilter}|${timeFilter}`;
      const last = lastFetchRef.current;

      if (last.center && last.filterKey === filterKey) {
        const [lastLng, lastLat] = last.center;
        const distKm = haversineKm(lastLat, lastLng, lat, lng);

        const lastR = last.radiusKm || 0;
        const radiusChanged =
          !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25;

        const minMoveKm = Math.max(50, radiusKm * 0.25);

        if (!radiusChanged && distKm < minMoveKm) {
          return;
        }
      }

      try {
        setStatus("Loading posts...");
        setLoadingPosts(true);
        setLoadError("");

        const catCode = categoryCode(mainFilter);
        const subCatParam =
          subFilter && subFilter.toUpperCase() !== "ALL" && subFilter !== "All"
            ? subFilter
            : "";

        const newPosts = await fetchPosts({
          category: catCode,
          subCategory: subCatParam,
          time: "",
          lat,
          lon: lng,
          radiusKm,
        });

        if (cancelled) return;

        const existing = allPostsRef.current || [];
        const byId = new Map();

        for (const p of existing) {
          if (p && typeof p.id !== "undefined") byId.set(p.id, p);
        }
        if (Array.isArray(newPosts)) {
          for (const p of newPosts) {
            if (p && typeof p.id !== "undefined" && !byId.has(p.id)) {
              byId.set(p.id, p);
            }
          }
        }

        allPostsRef.current = Array.from(byId.values());

        lastFetchRef.current = {
          center: [...centerArr],
          radiusKm,
          filterKey,
        };

        rebuildMarkersForFilters(timeFilter);
        setLoadingPosts(false);
      } catch (err) {
        console.error("Erreur chargement posts:", err);
        if (!cancelled) {
          setStatus("Error loading posts.");
          setLoadError("Error loading posts.");
          setLoadingPosts(false);
        }
      }
    }

    load();

    return () => {
      cancelled = true;
    };
  }, [viewParams, mainFilter, subFilter, timeFilter, mapRef, rebuildMarkersForFilters, markersRef, expandedElRef]);

  const handleIncomingPost = useCallback(
    (p) => {
      allPostsRef.current = [...allPostsRef.current, p];

      const catCode = categoryCode(mainFilter);
      if (catCode) {
        const pc = (p.category || p.Category || "").toString().toUpperCase();
        if (pc !== catCode) return;
      }
      if (!matchesSubFilter(p, subFilter)) return;
      if (!matchesTimeFilter(p.created_at || p.CreatedAt, timeFilter)) return;

      createMarkerForPost(p, mapRef, markersRef, expandedElRef);
      setVisiblePosts((current) => [...current, p]);
    },
    [mainFilter, subFilter, timeFilter, mapRef, markersRef, expandedElRef]
  );

  return {
    status,
    visiblePosts,
    loadingPosts,
    loadError,
    handleIncomingPost,
  };
}
EOF

echo "✅ Files written."

echo ""
echo "Next:"
echo "  npm run dev"
echo "or build/deploy as you do normally."
echo ""
echo "Tip: open DevTools Network -> you should see /api/posts on first page load."
