import { useCallback, useEffect, useRef, useState } from "react";
import { fetchPosts } from "../../api/client";
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
import { haversineKm } from "./mapGeo";
import { createMarkerForPost } from "./markerManager";

function getId(p) {
  return p?.id ?? p?._id ?? null;
}

function sameIdList(a, b) {
  if (a === b) return true;
  if (!Array.isArray(a) || !Array.isArray(b)) return false;
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (getId(a[i]) !== getId(b[i])) return false;
  }
  return true;
}

function asText(v) {
  if (v == null) return "";
  return String(v);
}

function postHaystack(p) {
  const title = asText(p?.title || p?.text || p?.body || "");
  const snippet = asText(p?.snippet || p?.summary || p?.content || "");
  const author = asText(p?.author || p?.Author || p?.user || p?.username || "");
  const city = asText(p?.city || p?.location_name || p?.place || "");
  const cat = asText(p?.category || p?.Category || "");
  const sub = asText(p?.sub_category || p?.subCategory || "");
  return `${title} ${snippet} ${author} ${city} ${cat} ${sub}`.toLowerCase();
}

function matchesSearch(p, qRaw) {
  const q = (qRaw || "").toString().trim().toLowerCase();
  if (!q) return true;

  const author = asText(p?.author || p?.Author || p?.user || p?.username || "").toLowerCase();
  const hay = postHaystack(p);

  const terms = q.split(/\s+/).map((t) => t.trim()).filter(Boolean);
  for (const t of terms) {
    if (t.startsWith("@")) {
      const want = t.slice(1).trim();
      if (!want) continue;
      if (!author.includes(want)) return false;
      continue;
    }
    if (!hay.includes(t)) return false;
  }
  return true;
}

export function usePostsEngine({
  mapRef,
  viewParams,
  mainFilter,
  subFilter,
  timeFilter,
  searchQuery,
  markersRef,
  expandedElRef,
  theme = "blue",
}) {
  const allPostsRef = useRef([]);
  const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "" });
  const delayedFetchRef = useRef(null);

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

  const syncMarkers = useCallback(
    (visible) => {
      const map = mapRef.current;
      if (!map) return;

      // If user is reading an expanded overlay, don't touch markers
      if (expandedElRef?.current) return;

      const wanted = new Map();
      for (const post of visible) {
        const id = getId(post);
        if (id != null) wanted.set(id, post);
      }

      const cur = markersRef.current || [];
      const next = [];

      // remove markers not wanted (smooth fade-out)
      for (const item of cur) {
        const id = item?.id;
        if (id == null || !wanted.has(id)) {
          try {
            const el = item?.el;
            if (el) {
              el.classList.add("sw-disappear");
              el.style.pointerEvents = "none";
            }
          } catch {}

          // let CSS transition play, then remove from map
          setTimeout(() => {
            try { if (item?.el && item.el.__swUnmount) item.el.__swUnmount(); } catch {}
            try { item?.marker?.remove?.(); } catch {}
          }, 840);
        } else {
          next.push(item);
        }
      }

      markersRef.current = next;

      const have = new Set(next.map((x) => x.id));

      // add missing markers
      for (const post of visible) {
        const id = getId(post);
        if (id == null || have.has(id)) continue;
        createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme);
      }
    },
    [mapRef, markersRef, expandedElRef, theme]
  );

  const computeVisible = useCallback(
    (tf) => {
      const posts = allPostsRef.current || [];
      const catCode = categoryCode(mainFilter);

      return 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 || p.createdAt, tf)) return false;
        if (!matchesSearch(p, searchQuery)) return false;
        return true;
      });
    },
    [mainFilter, subFilter, searchQuery]
  );

  const refreshVisibleAndMarkers = useCallback(
    (tf) => {
      const v = computeVisible(tf);

      // reduce wall blink: don't update if same ids
      setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v));

      // keep status silent (no annoying bubbles)
      setStatus("");

      syncMarkers(v);
    },
    [computeVisible, syncMarkers]
  );

  // On filter/search changes: recompute + sync (NO clear-all rebuild)
  useEffect(() => {
    refreshVisibleAndMarkers(timeFilter);
  }, [timeFilter, mainFilter, subFilter, searchQuery, refreshVisibleAndMarkers]);

  // Fetch on view change (moveend)
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (!viewParams.center) return;

    let cancelled = false;

    async function load() {
      const mapObj = mapRef.current;

      // If expanded overlay is open: delay fetch (do NOT disturb UI)
      if (expandedElRef?.current) {
        if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
        delayedFetchRef.current = setTimeout(load, 1200);
        return;
      }

      // Respect ignore window if any
      try {
        const until = mapObj?.__swIgnoreFetchUntil || 0;
        if (until && Date.now() < until) {
          const wait = Math.min(2500, until - Date.now());
          if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
          delayedFetchRef.current = setTimeout(load, wait + 25);
          return;
        }
      } catch {}

      const [lng, lat] = viewParams.center;
      const radiusKm = viewParams.radiusKm || 750;
      const filterKey = `${mainFilter}|${subFilter}`;

      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(40, radiusKm * 0.22);

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

      try {
        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) {
          const id = getId(p);
          if (id != null) byId.set(id, p);
        }
        if (Array.isArray(newPosts)) {
          for (const p of newPosts) {
            const id = getId(p);
            if (id != null && !byId.has(id)) byId.set(id, p);
          }
        }

        allPostsRef.current = Array.from(byId.values());
        lastFetchRef.current = { center: [...viewParams.center], radiusKm, filterKey };

        // Smooth update: sync markers + wall without clearing everything
        refreshVisibleAndMarkers(timeFilter);

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

    load();
    return () => {
      cancelled = true;
      if (delayedFetchRef.current) {
        try { clearTimeout(delayedFetchRef.current); } catch {}
        delayedFetchRef.current = null;
      }
    };
  }, [viewParams, mainFilter, subFilter, mapRef, timeFilter, expandedElRef, refreshVisibleAndMarkers]);

  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 || p.createdAt, timeFilter)) return;
      if (!matchesSearch(p, searchQuery)) return;

      // add marker if missing
      const id = getId(p);
      const have = new Set((markersRef.current || []).map((x) => x.id));
      if (id != null && !have.has(id)) {
        createMarkerForPost(p, mapRef, markersRef, expandedElRef, theme);
      }

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

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