import React, { useCallback, useEffect, useRef, useState } from "react";
import "maplibre-gl/dist/maplibre-gl.css";
import "../../styles/map.css";
import "../../styles/mapMarkers.css";
import "../../styles/posts.css";
import "../../styles/filters.css";

import { createPost, fetchPostPreview, resolveAssetRef } from "../../api/client";
import { LiveKitBroadcast, LiveKitViewerModal } from "../Live";
import {
  FILTER_MAIN_CATEGORIES,
  FILTER_CATEGORY_MAP,
  CREATE_CATEGORY_MAP,
} from "./mapConfig";
import { useMapCore } from "./useMapCore";
import { useUserPosition } from "./useUserPosition";
import { usePostsEngine } from "./usePostsEngine";
import { openCenteredOverlay } from "./markerManager";
import { useAuth } from "../../auth/AuthContext";
import FilterButtons from "../Filters/FilterButtons";
import TimeFilterButtons from "../Filters/TimeFilterButtons";
import SmartSearchBar from "../Search/SmartSearchBar";
import UserProfile from "../Profile/UserProfile";
import { fetchPostById, fetchPostByUrl } from "../../api/client";
import { trackEvent } from "../../utils/analytics";

const CONTENT_CREATOR_MODES = [
  {
    id: "live",
    label: "Live video",
    icon: "fa-solid fa-broadcast-tower",
    hint: "Broadcast a live scene with a headline or link.",
    detail: [
      "Drop the stream URL or quick headline to pin the feed instantly.",
      "We’ll open the location where you place the crosshair so viewers know where to tune.",
    ],
  },
  {
    id: "link",
    label: "Link story",
    icon: "fa-solid fa-link",
    hint: "Share a URL and we’ll prefill the preview.",
    detail: [
      "Paste a news link, playlist, or update so we can fetch an image and snippet.",
      "Choose the most relevant pin so the story belongs to the right neighborhood.",
    ],
  },
  {
    id: "photo",
    label: "Photo story",
    icon: "fa-solid fa-camera-retro",
    hint: "Upload or link an image with context.",
    detail: [
      "Upload a photo set or paste an image URL from your gallery.",
      "Caption quickly so the community knows what they’re looking at.",
    ],
  },
  {
    id: "text",
    label: "Written update",
    icon: "fa-solid fa-pen-to-square",
    hint: "Type a short report or microblog.",
    detail: [
      "Write the key message in the title field (or add more context in the body).",
      "Use subcategory filters to mark the tone: News, Event, Market, etc.",
    ],
  },
  {
    id: "other",
    label: "Other",
    icon: "fa-solid fa-ellipsis",
    hint: "Custom content that spans media types.",
    detail: [
      "Mix media, public drafts, or reference notes in one place.",
      "Tag a region or Island to give the update a home.",
    ],
  },
];

const MODE_FLOW_CONFIG = {
  live: {
    hero: "Pin where the live scene is happening and paste your stream link or headline.",
    placeholder: "Streaming link or headline",
    hint: "We’ll treat this as a live broadcast and show it as soon as the pin lands.",
    bodyPlaceholder: "Context for viewers, optional.",
  },
  link: {
    hero: "Drop the link, describe what it is, and pin the relevant spot.",
    placeholder: "Article or playlist URL",
    hint: "We’ll auto-fetch a preview once the link is detected.",
    bodyPlaceholder: "What matters about this link?",
  },
  photo: {
    hero: "Snap or upload a photo and pin the place you captured it.",
    placeholder: "Photo caption or image URL",
    hint: "Add a quick caption so the community knows what they’re seeing.",
    bodyPlaceholder: "Describe the scene in a few words.",
  },
  text: {
    hero: "Type a short update and pin the area you’re reporting from.",
    placeholder: "Headline or short note",
    hint: "Readers should understand the gist before opening the full card.",
    bodyPlaceholder: "Provide more context, opinions, or call to actions.",
  },
  other: {
    hero: "Combine media, notes, or references and pin wherever it matters.",
    placeholder: "Describe your update",
    hint: "This mode is flexible—add whatever helps the reader.",
    bodyPlaceholder: "Add links, attachments, or extra context if needed.",
  },
};

const LINK_PREVIEW_PLACEHOLDER =
  "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNDAiIGhlaWdodD0iMTQwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByeD0iMTIiIHJ5PSIxMiIgZmlsbD0iIzBiMTIyMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjOTRhM2I4IiBmb250LWZhbWlseT0iSW50ZXIsIEFyaWFsIiBmb250LXNpemU9IjI0Ij5MaW5rPC90ZXh0Pjwvc3ZnPg==";

export default function MapView({
  theme = "dark",
  onPostsState,
  selectedPost,
  onSelectPost,
  selectedIsland,
  onSelectIsland,
  onOpenIslands,
}) {
  const { authenticated, username, token, needsEmailVerification } = useAuth();
  const CLUSTERS_ENABLED = false;
  const HEATMAP_ENABLED = false;

  const { containerRef, mapRef, markersRef, expandedElRef, viewParams, hasLastView } =
    useMapCore(theme);

  const [mainFilter, setMainFilter] = useState("All");
  const TIME_FILTER_KEY = "sociowire:timeHours";
  const [timeHours, setTimeHours] = useState(() => {
    try {
      const v = localStorage.getItem(TIME_FILTER_KEY);
      const n = Number(v);
      return Number.isFinite(n) && n > 0 ? n : 336;
    } catch {
      return 336;
    }
  });
  const [subFilter, setSubFilter] = useState("All");
  const [mainNewsOnly, setMainNewsOnly] = useState(false);

  const debugEnabled = (() => {
    try {
      return typeof window !== "undefined" &&
        new URLSearchParams(window.location.search || "").has("debug");
    } catch {
      return false;
    }
  })();

  useEffect(() => {
    try {
      localStorage.setItem(TIME_FILTER_KEY, String(timeHours));
    } catch {}
  }, [timeHours]);

  useEffect(() => {
    if (clusterFocus) setClusterFocus(null);
    if (mainNewsOnly) setMainNewsOnly(false);
  }, [timeHours]);

  const stageRef = useRef(null);
  const [showSubcats, setShowSubcats] = useState(true);

  const userPosition = useUserPosition(mapRef, hasLastView);

  const [searchQuery, setSearchQuery] = useState("");
  const skipAutoZoomRef = useRef(false);
  const [clusterFocus, setClusterFocus] = useState(null);
  const [forceFullPostId, setForceFullPostId] = useState(null);

  // Profile state (selectedIsland is now passed from App.jsx)
  const [selectedProfile, setSelectedProfile] = useState(null);

  // Live broadcast state
  const [showLiveBroadcast, setShowLiveBroadcast] = useState(false);
  const [activeLivePost, setActiveLivePost] = useState(null);
  const [liveCoords, setLiveCoords] = useState(null);
  const openedPostRef = useRef(null);
  const queryPostRef = useRef(false);
  const overlayTimerRef = useRef(null);
  const panTimerRef = useRef(null);
  const pendingOpenPostIdRef = useRef(null);
  const pendingOpenFullRef = useRef(false);
  const searchFitQueryRef = useRef("");
  const searchUserMovedRef = useRef(false);

  const openOverlayWhenReady = useCallback(
    (post, opts = {}) => {
      if (!post) return;
      const { fullScreen = false } = opts;
      const tryOpen = () => {
        const map = mapRef.current;
        if (!map) return false;
        openCenteredOverlay({ map, post, theme, fullScreen });
        return true;
      };

      if (tryOpen()) return;
      if (overlayTimerRef.current) clearInterval(overlayTimerRef.current);
      overlayTimerRef.current = setInterval(() => {
        if (tryOpen()) {
          clearInterval(overlayTimerRef.current);
          overlayTimerRef.current = null;
        }
      }, 220);
    },
    [mapRef, theme]
  );

  // Long-press detection for creating posts (2.5 seconds)
  // Cancels on: zoom, pan, multi-touch, finger movement > 10px
  const longPressTimerRef = useRef(null);
  const longPressStartPos = useRef(null);
  const longPressCallbackRef = useRef(null);
  const LONG_PRESS_DURATION = 1000;
  const LONG_PRESS_MOVE_THRESHOLD = 10; // pixels

  const cancelLongPress = useCallback(() => {
    if (longPressTimerRef.current) {
      clearTimeout(longPressTimerRef.current);
      longPressTimerRef.current = null;
    }
    longPressStartPos.current = null;
  }, []);

  // Cancel long press on ANY map interaction
  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    const cancel = () => cancelLongPress();

    map.on('zoomstart', cancel);
    map.on('movestart', cancel);
    map.on('pitchstart', cancel);
    map.on('rotatestart', cancel);
    map.on('zoom', cancel);
    map.on('move', cancel);
    map.on('pitch', cancel);
    map.on('rotate', cancel);
    map.on('dragstart', cancel);
    map.on('drag', cancel);

    return () => {
      map.off('zoomstart', cancel);
      map.off('movestart', cancel);
      map.off('pitchstart', cancel);
      map.off('rotatestart', cancel);
      map.off('zoom', cancel);
      map.off('move', cancel);
      map.off('pitch', cancel);
      map.off('rotate', cancel);
      map.off('dragstart', cancel);
      map.off('drag', cancel);
    };
  }, [mapRef.current, cancelLongPress]);

  // Touch/mouse event handlers for long-press
  // Re-run when viewParams changes (indicates map is ready)
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const handleTouchStart = (e) => {
      // Multi-touch = cancel immediately
      if (e.touches.length !== 1) {
        cancelLongPress();
        return;
      }

      // Ignore UI elements
      if (e.target.closest('.map-overlay') || e.target.closest('.sw-marker') || e.target.closest('button')) {
        return;
      }

      const touch = e.touches[0];
      longPressStartPos.current = { x: touch.clientX, y: touch.clientY };

      longPressTimerRef.current = setTimeout(() => {
        if (longPressStartPos.current && longPressCallbackRef.current) {
          longPressCallbackRef.current();
        }
        cancelLongPress();
      }, LONG_PRESS_DURATION);
    };

    const handleTouchMove = (e) => {
      // Multi-touch = cancel
      if (e.touches.length !== 1) {
        cancelLongPress();
        return;
      }

      // Check if finger moved too far from start position
      if (longPressStartPos.current && e.touches[0]) {
        const dx = e.touches[0].clientX - longPressStartPos.current.x;
        const dy = e.touches[0].clientY - longPressStartPos.current.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        if (distance > LONG_PRESS_MOVE_THRESHOLD) {
          cancelLongPress();
        }
      }
    };

    const handleTouchEnd = () => cancelLongPress();

    const handleMouseDown = (e) => {
      if (e.target.closest('.map-overlay') || e.target.closest('.sw-marker') || e.target.closest('button')) {
        return;
      }
      longPressStartPos.current = { x: e.clientX, y: e.clientY };
      longPressTimerRef.current = setTimeout(() => {
        if (longPressStartPos.current && longPressCallbackRef.current) {
          longPressCallbackRef.current();
        }
        cancelLongPress();
      }, LONG_PRESS_DURATION);
    };

    const handleMouseMove = (e) => {
      if (longPressStartPos.current) {
        const dx = e.clientX - longPressStartPos.current.x;
        const dy = e.clientY - longPressStartPos.current.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        if (distance > LONG_PRESS_MOVE_THRESHOLD) {
          cancelLongPress();
        }
      }
    };

    const handleMouseUp = () => cancelLongPress();
    const handleWheel = () => cancelLongPress();

    container.addEventListener('touchstart', handleTouchStart, { passive: true });
    container.addEventListener('touchmove', handleTouchMove, { passive: true });
    container.addEventListener('touchend', handleTouchEnd);
    container.addEventListener('touchcancel', handleTouchEnd);
    container.addEventListener('mousedown', handleMouseDown);
    container.addEventListener('mousemove', handleMouseMove);
    container.addEventListener('mouseup', handleMouseUp);
    container.addEventListener('mouseleave', handleMouseUp);
    container.addEventListener('wheel', handleWheel, { passive: true });

    return () => {
      container.removeEventListener('touchstart', handleTouchStart);
      container.removeEventListener('touchmove', handleTouchMove);
      container.removeEventListener('touchend', handleTouchEnd);
      container.removeEventListener('touchcancel', handleTouchEnd);
      container.removeEventListener('mousedown', handleMouseDown);
      container.removeEventListener('mousemove', handleMouseMove);
      container.removeEventListener('mouseup', handleMouseUp);
      container.removeEventListener('mouseleave', handleMouseUp);
      container.removeEventListener('wheel', handleWheel);
      cancelLongPress();
    };
  }, [viewParams, cancelLongPress]);

  const fitBoundsWhenReady = useCallback(
    (bounds, opts = {}) => {
      const tryFit = () => {
        const map = mapRef.current;
        if (!map) return false;
        try {
          map.fitBounds(bounds, opts);
        } catch {}
        return true;
      };
      if (tryFit()) return;
      if (panTimerRef.current) clearInterval(panTimerRef.current);
      panTimerRef.current = setInterval(() => {
        if (tryFit()) {
          clearInterval(panTimerRef.current);
          panTimerRef.current = null;
        }
      }, 220);
    },
    [mapRef]
  );

  const panToPostWhenReady = useCallback(
    (post, zoom = 12) => {
      const lat = Number(post?.lat);
      const lon = Number(post?.lon);
      if (!Number.isFinite(lat) || !Number.isFinite(lon)) return;

      const tryPan = () => {
        const map = mapRef.current;
        if (!map) return false;
        try {
          map.easeTo({ center: [lon, lat], zoom, duration: 700 });
        } catch {}
        return true;
      };

      if (tryPan()) return;
      if (panTimerRef.current) clearInterval(panTimerRef.current);
      panTimerRef.current = setInterval(() => {
        if (tryPan()) {
          clearInterval(panTimerRef.current);
          panTimerRef.current = null;
        }
      }, 220);
    },
    [mapRef]
  );

  const { status, debugStatus, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate, handlePostDelete } =
    usePostsEngine({
      theme,
      mapRef,
      viewParams,
      mainFilter,
      subFilter,
      timeHours,
      searchQuery,
      clusterFocus,
      mainNewsOnly,
      clustersEnabled: CLUSTERS_ENABLED,
      heatmapEnabled: HEATMAP_ENABLED,
      forceFullPostId,
      markersRef,
      expandedElRef,
      onAutoWidenTimeFilter: () => setTimeHours(336),
      onSelectPost,
      username,
      debugEnabled,
    });

  const [isCreating, setIsCreating] = useState(false);
  const [draftTitle, setDraftTitle] = useState("");
  const [draftBody, setDraftBody] = useState("");
  const [draftUrl, setDraftUrl] = useState("");
  const [draftSource, setDraftSource] = useState("");
  const [draftPublishedAt, setDraftPublishedAt] = useState("");
  const [draftCategory, setDraftCategory] = useState("News");
  const [draftSubCategory, setDraftSubCategory] = useState(
    CREATE_CATEGORY_MAP.News[0]
  );
  const [contentMode, setContentMode] = useState(CONTENT_CREATOR_MODES[0].id);
  const currentContentMode =
    CONTENT_CREATOR_MODES.find((mode) => mode.id === contentMode) ||
    CONTENT_CREATOR_MODES[0];
  const currentFlow = MODE_FLOW_CONFIG[contentMode] || MODE_FLOW_CONFIG.text;
  const [draftCoords, setDraftCoords] = useState(null);
  const [modeSelectorVisible, setModeSelectorVisible] = useState(false);
  const [modeChosen, setModeChosen] = useState(false);
  const [draftImage, setDraftImage] = useState("");
  const [draftImageSmall, setDraftImageSmall] = useState("");
  const [draftImageMedium, setDraftImageMedium] = useState("");
  const [draftImageLarge, setDraftImageLarge] = useState("");
  const [draftImageFile, setDraftImageFile] = useState(null);
  const [draftImagePreview, setDraftImagePreview] = useState(
    LINK_PREVIEW_PLACEHOLDER
  );
  const [draftMediaUrls, setDraftMediaUrls] = useState([]);
  const [useCustomImage, setUseCustomImage] = useState(contentMode !== "link");
  const [postVisibility, setPostVisibility] = useState("public");
  const [postGroupId, setPostGroupId] = useState("");
  const [postUsers, setPostUsers] = useState("");
  const [imageVisibility, setImageVisibility] = useState("public");
  const [imageGroupId, setImageGroupId] = useState("");
  const [imageUsers, setImageUsers] = useState("");
  const [uploadingImage, setUploadingImage] = useState(false);
  const [urlPreviewLoading, setUrlPreviewLoading] = useState(false);
  const [urlPreviewError, setUrlPreviewError] = useState("");
  const [titleTouched, setTitleTouched] = useState(false);
  const [bodyTouched, setBodyTouched] = useState(false);
  const [imageTouched, setImageTouched] = useState(false);
  const [isSaving, setIsSaving] = useState(false);
  const [saveError, setSaveError] = useState("");
  const [createStep, setCreateStep] = useState(0);
  const previewTimerRef = useRef(null);
  const lastPreviewUrlRef = useRef("");

  const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"];

  const formatPreviewDate = (value) => {
    if (!value) return "";
    const d = new Date(value);
    if (Number.isNaN(d.getTime())) return String(value);
    return d.toLocaleString();
  };

  const isLikelyUrl = (value) => /^https?:\/\//i.test(String(value || "").trim());

  const getSubcatIcon = (main, sub) => {
    const M = {
      All: {
        All: "fa-solid fa-layer-group",
      },

      News: {
        All: "fa-solid fa-layer-group",
        World: "fa-solid fa-globe",
        Politics: "fa-solid fa-landmark-flag",
        Tech: "fa-solid fa-microchip",
        Finance: "fa-solid fa-chart-line",
        Local: "fa-solid fa-location-dot",
      },

      Friends: {
        All: "fa-solid fa-layer-group",
        Nearby: "fa-solid fa-location-crosshairs",
        Chats: "fa-solid fa-comments",
        Groups: "fa-solid fa-user-group",
        Stories: "fa-solid fa-book-open",
        Favs: "fa-solid fa-heart",
      },

      Events: {
        All: "fa-solid fa-layer-group",
        Today: "fa-solid fa-calendar-day",
        Weekend: "fa-solid fa-calendar-week",
        Music: "fa-solid fa-music",
        Sports: "fa-solid fa-football",
        Local: "fa-solid fa-location-dot",
      },

      Market: {
        All: "fa-solid fa-layer-group",
        Actu: "fa-solid fa-newspaper",
        Tech: "fa-solid fa-microchip",
        Finan: "fa-solid fa-money-bill-trend-up",
        Sport: "fa-solid fa-basketball",
        Deals: "fa-solid fa-tags",
      },
    };

    return (M[main] && M[main][sub]) || "";
  };

  useEffect(() => {
    if (!bottomCategories.length) return;
    if (!subFilter || !bottomCategories.includes(subFilter)) setSubFilter("All");
  }, [mainFilter, bottomCategories, subFilter]);

  useEffect(() => {
    if (!onPostsState) return;
    onPostsState({
      status,
      posts: visiblePosts,
      loading: loadingPosts,
      error: loadError,
    });
  }, [status, visiblePosts, loadingPosts, loadError, onPostsState]);

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

    const lng = selectedPost.lon ?? selectedPost.lng;
    const lat = selectedPost.lat ?? selectedPost.latitude;

    if (typeof lng === "number" && typeof lat === "number") {
      map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 });
    }
    if (selectedPost?.id && openedPostRef.current !== selectedPost.id) {
      openedPostRef.current = selectedPost.id;
      const wantFull = pendingOpenFullRef.current && pendingOpenPostIdRef.current === selectedPost.id;
      openOverlayWhenReady(selectedPost, { fullScreen: wantFull });
      if (wantFull) {
        pendingOpenFullRef.current = false;
        pendingOpenPostIdRef.current = null;
      }
    }
  }, [selectedPost, mapRef, theme]);

  useEffect(() => {
    trackEvent("filter_main_change", { filter: mainFilter });
  }, [mainFilter]);

  useEffect(() => {
    trackEvent("filter_sub_change", { filter: subFilter, parent: mainFilter });
  }, [subFilter, mainFilter]);

  useEffect(() => {
    trackEvent("filter_time_change", { hours: timeHours });
  }, [timeHours]);

  useEffect(() => {
    if (queryPostRef.current) return;
    const qs = new URLSearchParams(window.location.search || "");
    let idRaw = qs.get("post_id") || qs.get("id") || qs.get("p");
    let wasSharedLink = false;
    if (!idRaw) {
      const path = (window.location.pathname || "").trim();
      const m = path.match(/^\/p\/(\d+)/);
      if (m && m[1]) {
        idRaw = m[1];
        wasSharedLink = true;
        // Clean URL immediately - no post_id in URL, just stay on root
        try {
          window.history.replaceState(null, "", "/");
        } catch {}
      }
    } else {
      // If arrived with ?post_id=123, also clean it
      wasSharedLink = true;
      try {
        window.history.replaceState(null, "", "/");
      } catch {}
    }
    if (!idRaw) return;
    queryPostRef.current = true;
    const id = Number(idRaw);
    if (!Number.isFinite(id) || id <= 0) return;
    pendingOpenPostIdRef.current = id;
    pendingOpenFullRef.current = true;
    fetchPostById(id).then((post) => {
      if (!post) {
        // Post doesn't exist - show message and redirect
        alert("This post no longer exists or was deleted.");
        try { window.history.replaceState(null, "", "/"); } catch {}
        pendingOpenPostIdRef.current = null;
        pendingOpenFullRef.current = false;
        return;
      }
      onSelectPost?.(post);
      openOverlayWhenReady(post, { fullScreen: true });
      pendingOpenPostIdRef.current = null;
      pendingOpenFullRef.current = false;
    });
  }, [onSelectPost, openOverlayWhenReady]);

  useEffect(() => {
    if (queryPostRef.current) return;
    if (!CLUSTERS_ENABLED) return;
    const path = (window.location.pathname || "").trim();
    const clusterMatch = path.match(/^\/cluster\/[^/]+\/\d{8}/);
    if (!clusterMatch) return;
    queryPostRef.current = true;
    const fullUrl = `${window.location.origin}${clusterMatch[0]}`;
    fetchPostByUrl(fullUrl).then((post) => {
      if (!post) return;
      const items = Array.isArray(post.cluster_items) ? post.cluster_items : [];
      const ids = Array.isArray(post.cluster_members) ? post.cluster_members : [];
      const points = items
        .map((it) => ({ lat: Number(it?.lat), lon: Number(it?.lon) }))
        .filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lon));
      const mainPost = { ...post };
      if (!Array.isArray(mainPost.cluster_items) || mainPost.cluster_items.length === 0) {
        mainPost.cluster_items = items;
      }
      setClusterFocus({
        key: post.cluster_key || "",
        ids,
        posts: items.map((it) => ({
          id: Number(it.id || it.ID) || 0,
          title: it.title || "Untitled",
          snippet: it.snippet || "",
          category: "NEWS",
          sub_category: "World",
          lat: Number(it.lat),
          lon: Number(it.lon),
          url: it.url || "",
          source: it.source || "",
          image_small: it.image_small || "",
          image_medium: it.image_medium || "",
          image_large: it.image_large || "",
          created_at: it.published_at || "",
          published_at: it.published_at || "",
        })).concat([mainPost]),
        points,
        center: { lat: Number(post.lat), lon: Number(post.lon) },
      });
      onSelectPost?.(mainPost);
      panToPostWhenReady(mainPost, 12);
      openOverlayWhenReady(mainPost, { fullScreen: true });
    });
  }, [mapRef, onSelectPost, openOverlayWhenReady, panToPostWhenReady]);

  useEffect(() => {
    if (!CLUSTERS_ENABLED || !HEATMAP_ENABLED) return;
    const handler = (ev) => {
      const detail = ev?.detail || {};
      const mode = detail.mode || "click";
      const skipPan = !!detail.skipPan;
      const key = (detail.clusterKey || "").toString();
      const members = Array.isArray(detail.members) ? detail.members : [];
      const postId = Number(detail.postId || 0);
      const ids = members.length ? Array.from(new Set([...members, postId].filter(Boolean))) : (postId ? [postId] : []);
      const items = Array.isArray(detail.items) ? detail.items : [];
      const mainPost = detail.post && typeof detail.post === "object" ? detail.post : null;

      const toExtraPosts = (list, basePost) => {
        const out = [];
        if (basePost) {
          const withItems = { ...basePost };
          if (!Array.isArray(withItems.cluster_items) || withItems.cluster_items.length === 0) {
            withItems.cluster_items = list;
          }
          out.push(withItems);
        }
        for (const it of list) {
          if (!it) continue;
          const id = Number(it.id || it.ID);
          out.push({
            id: Number.isFinite(id) ? id : 0,
            title: it.title || it.Title || "Untitled",
            snippet: it.snippet || "",
            category: "NEWS",
            sub_category: "World",
            lat: Number(it.lat),
            lon: Number(it.lon),
            url: it.url || "",
            source: it.source || "",
            image_small: it.image_small || "",
            image_medium: it.image_medium || "",
            image_large: it.image_large || "",
            created_at: it.published_at || "",
            published_at: it.published_at || "",
          });
        }
        return out;
      };

      const applyClusterFocus = (list, basePost, points) => {
        setClusterFocus((prev) => {
          if (prev && prev.key && key && prev.key === key) {
            return mode === "hover" ? prev : null;
          }
          return {
            key,
            ids,
            posts: toExtraPosts(list, basePost),
            points,
            center: detail.center || null,
            mode,
          };
        });
      };

      const map = mapRef.current;
      if (!map) return;
      try { map.__swForceFetchAt = Date.now(); } catch {}

      const points = Array.isArray(detail.points) ? detail.points : [];
      const center = detail.center || {};
      const ensurePoints = (list) => {
        const pts = list
          .map((it) => ({ lat: Number(it?.lat), lon: Number(it?.lon) }))
          .filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lon));
        if (Number.isFinite(center.lat) && Number.isFinite(center.lon)) {
          pts.push({ lat: center.lat, lon: center.lon });
        }
        return pts;
      };

      const usePoints = (pts) => {
        if (skipPan) return;
        const valid = pts.filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lon));
        if (valid.length >= 2) {
          let minLat = 90, maxLat = -90, minLon = 180, maxLon = -180;
          for (const p of valid) {
            minLat = Math.min(minLat, p.lat);
            maxLat = Math.max(maxLat, p.lat);
            minLon = Math.min(minLon, p.lon);
            maxLon = Math.max(maxLon, p.lon);
          }
          fitBoundsWhenReady([[minLon, minLat], [maxLon, maxLat]], { padding: 80, duration: 700 });
          return;
        }
        const lat = Number(center.lat);
        const lon = Number(center.lon);
        if (Number.isFinite(lat) && Number.isFinite(lon)) {
          panToPostWhenReady({ lat, lon }, 12);
        }
      };

      if (!items.length && mainPost?.url) {
        fetchPostByUrl(mainPost.url).then((full) => {
          if (full && Array.isArray(full.cluster_items) && full.cluster_items.length > 0) {
            const fullItems = full.cluster_items;
            const pts = ensurePoints(fullItems);
            applyClusterFocus(fullItems, { ...mainPost, ...full, cluster_items: fullItems }, pts);
            usePoints(pts);
          } else {
            applyClusterFocus(items, mainPost, points);
            usePoints(points);
          }
        });
        return;
      }

      const pts = ensurePoints(items);
      applyClusterFocus(items, mainPost, pts);
      usePoints(pts);
    };
    window.addEventListener("sociowire:cluster-heatmap", handler);
    return () => window.removeEventListener("sociowire:cluster-heatmap", handler);
  }, [mapRef, fitBoundsWhenReady, panToPostWhenReady, CLUSTERS_ENABLED, HEATMAP_ENABLED]);

  useEffect(() => {
    if (!CLUSTERS_ENABLED || !HEATMAP_ENABLED) return;
    const handler = () => {
      setClusterFocus((prev) => (prev?.mode === "hover" ? null : prev));
    };
    window.addEventListener("sociowire:cluster-heatmap-clear", handler);
    return () => window.removeEventListener("sociowire:cluster-heatmap-clear", handler);
  }, [CLUSTERS_ENABLED, HEATMAP_ENABLED]);

  useEffect(() => {
    const wantId = pendingOpenPostIdRef.current;
    if (!wantId || !Array.isArray(visiblePosts) || !visiblePosts.length) return;
    // If pendingOpenFullRef is true, we're waiting for fetchPostById to validate the post exists
    // Don't intercept with cached data - let the fetch complete first
    if (pendingOpenFullRef.current) return;
    const map = mapRef.current;
    const overlay = map?.__swCenteredOverlay;
    if (overlay && overlay.postId === wantId) {
      pendingOpenPostIdRef.current = null;
      return;
    }
    const match = visiblePosts.find((p) => (p?.id || p?.ID) === wantId);
    if (!match) return;
    pendingOpenPostIdRef.current = null;
    onSelectPost?.(match);
    openOverlayWhenReady(match, { fullScreen: false });
  }, [visiblePosts, onSelectPost, openOverlayWhenReady]);

  useEffect(() => {
    return () => {
      if (overlayTimerRef.current) clearInterval(overlayTimerRef.current);
      if (panTimerRef.current) clearInterval(panTimerRef.current);
    };
  }, []);

  useEffect(() => {
    const el = stageRef.current;
    if (!el || typeof IntersectionObserver === "undefined") return;

    const obs = new IntersectionObserver(
      (entries) => {
        const e = entries && entries[0];
        const ok = !!(e && e.isIntersecting && e.intersectionRatio > 0.15);
        setShowSubcats(ok);
      },
      { threshold: [0, 0.15, 0.3, 0.6] }
    );

    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  useEffect(() => {
    const proto = window.location.protocol === "https:" ? "wss" : "ws";
    const host =
      window.location.port === "5173"
        ? `${window.location.hostname}:8081`
        : window.location.host;
    const wsUrl = `${proto}://${host}/ws`;

    let ws;
    try {
      ws = new WebSocket(wsUrl);
    } catch {
      return;
    }

    ws.onmessage = (ev) => {
      try {
        const msg = JSON.parse(ev.data);
        if (msg.type === "new_post" && msg.post) {
          handleIncomingPost(msg.post);
          return;
        }
        if (msg.type === "post_update") {
          handlePostUpdate(msg);
          return;
        }
        if (msg.type === "post_delete") {
          handlePostDelete(msg);
          return;
        }
      } catch {}
    };

    return () => ws && ws.close();
  }, [handleIncomingPost, handlePostUpdate, handlePostDelete]);

  // Listen for live post created event (from LiveBroadcastModal)
  useEffect(() => {
    const onLivePostCreated = (e) => {
      if (e.detail && e.detail.id) {
        handleIncomingPost(e.detail);
      }
    };
    window.addEventListener('live-post-created', onLivePostCreated);
    return () => window.removeEventListener('live-post-created', onLivePostCreated);
  }, [handleIncomingPost]);

  useEffect(() => {
    const onLivePostUpdated = (e) => {
      if (e.detail && e.detail.id) {
        handlePostUpdate({ post_id: e.detail.id, post: e.detail });
      }
    };
    const onLivePostDeleted = (e) => {
      const postId = e.detail?.post_id || e.detail?.id;
      if (postId) {
        handlePostDelete({ post_id: postId });
        // Clear selected post and openedPostRef if it was deleted
        const selId = selectedPost?.id || selectedPost?.ID;
        if (selId && selId === postId) {
          openedPostRef.current = null;
          onSelectPost?.(null);
        }
      }
    };
    window.addEventListener('live-post-updated', onLivePostUpdated);
    window.addEventListener('live-post-deleted', onLivePostDeleted);
    return () => {
      window.removeEventListener('live-post-updated', onLivePostUpdated);
      window.removeEventListener('live-post-deleted', onLivePostDeleted);
    };
  }, [handlePostUpdate, handlePostDelete, selectedPost, onSelectPost]);

  useEffect(() => {
    const onLiveOpen = (e) => {
      if (e.detail) {
        setActiveLivePost(e.detail);
      }
    };
    window.addEventListener('livekit-open', onLiveOpen);
    return () => window.removeEventListener('livekit-open', onLiveOpen);
  }, []);

  const handleFlyToMe = () => {
    const map = mapRef.current;
    if (!map || !userPosition) return;
    map.flyTo({ center: userPosition, zoom: 9, speed: 1.2 });
  };

  const handlePickSuggestion = (item, { zoom }) => {
    const map = mapRef.current;
    if (!map) return;

    // Extract coords from item
    let coords = null;
    if (Array.isArray(item.coords) && item.coords.length === 2) {
      coords = item.coords; // [lon, lat]
    } else if (typeof item.lon === 'number' && typeof item.lat === 'number') {
      coords = [item.lon, item.lat];
    }

    // If picking a post, open the real post (with likes/comments) after fetch.
    const kind = (item?.kind || item?.type || "").toString().toLowerCase();
    const raw = item?.raw || item;
    const looksLikePost =
      kind === "post" ||
      kind === "posts" ||
      Number(raw?.post_id || 0) > 0 ||
      !!raw?.url_hash ||
      (!!raw?.url && !kind.includes("profile") && !kind.includes("place"));
    if (looksLikePost) {
      skipAutoZoomRef.current = true;
      setSearchQuery("");
      setMainFilter("All");
      setSubFilter("All");
      setTimeHours(336); // 2 weeks

      const postId = Number(raw?.post_id ?? raw?.id ?? item?.id ?? 0);
      const rawUrl = raw?.url || item?.url || "";
      if (Number.isFinite(postId) && postId > 0) {
        pendingOpenPostIdRef.current = postId;
        pendingOpenFullRef.current = false;
      }

      const focusPost = (post) => {
        if (!post) return;
        const lat = Number(post?.lat);
        const lon = Number(post?.lon);
        if (Number.isFinite(lat) && Number.isFinite(lon)) {
          const center = map.getCenter?.();
          const dz = (zoom || 12);
          const distKm = center ? Math.sqrt(Math.pow(center.lat - lat, 2) + Math.pow(center.lng - lon, 2)) * 111 : 999;
          const curZoom = map.getZoom?.() || dz;
          const targetZoom = distKm > 8 ? dz : Math.max(curZoom, Math.min(dz, curZoom + 0.8));
          map.easeTo({
            center: [lon, lat],
            zoom: targetZoom,
            duration: 650,
            essential: true
          });
          map.__swForceFetchAt = Date.now();
        }
        openedPostRef.current = post?.id || null;
        onSelectPost?.(post);
        openOverlayWhenReady(post, { fullScreen: false });
      };

      const stubPost = (() => {
        const lat = Number(raw?.lat ?? item?.lat);
        const lon = Number(raw?.lon ?? item?.lon);
        const id = Number(postId || 0);
        if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
        return {
          id: Number.isFinite(id) ? id : 0,
          title: raw?.title || item?.title || "Post",
          snippet: raw?.snippet || item?.snippet || "",
          category: raw?.category || item?.category || "NEWS",
          sub_category: raw?.sub_category || item?.sub_category || "",
          lat,
          lon,
          url: raw?.url || item?.url || "",
          image_small: raw?.image_small || item?.image_small || "",
          image_medium: raw?.image_medium || item?.image_medium || "",
          image_large: raw?.image_large || item?.image_large || "",
          created_at: raw?.created_at || item?.created_at || "",
          published_at: raw?.published_at || item?.published_at || "",
        };
      })();

      const tryOpen = async (attempt = 0) => {
        let post = null;
        if (Number.isFinite(postId) && postId > 0) {
          post = await fetchPostById(postId);
        }
        if (!post && rawUrl) {
          post = await fetchPostByUrl(rawUrl);
        }
        if (post) {
          pendingOpenPostIdRef.current = null;
          pendingOpenFullRef.current = false;
          focusPost(post);
          return;
        }
        if (attempt < 2) {
          setTimeout(() => tryOpen(attempt + 1), 650 + attempt * 400);
          return;
        }
        if (stubPost) {
          pendingOpenPostIdRef.current = null;
          pendingOpenFullRef.current = false;
          focusPost(stubPost);
        }
        if (coords) {
          pendingOpenPostIdRef.current = null;
          pendingOpenFullRef.current = false;
          map.easeTo({
            center: coords,
            zoom: zoom || 12,
            duration: 650,
            essential: true
          });
          map.__swForceFetchAt = Date.now();
        }
      };
      tryOpen();
      return;
    }

    if (!coords) {
      console.warn('[MapView] No coords for suggestion:', item);
      return;
    }

    console.log('[MapView] Flying to:', item.title, coords, 'zoom:', zoom);
    map.flyTo({
      center: coords,
      zoom: zoom || 12,
      speed: 1.4,
      essential: true
    });
    map.__swForceFetchAt = Date.now();

    if (kind === 'city' || kind === 'region' || kind === 'place' || kind === 'poi') {
      // Place picks should move the map without forcing a text filter.
      skipAutoZoomRef.current = false;
      setSearchQuery("");
      setForceFullPostId(null);
      return;
    }

    const q = (item.title || item.name || "").toString().trim();
    skipAutoZoomRef.current = false;
    setSearchQuery(q);
    if (q) {
      setMainFilter("All");
      setSubFilter("All");
      setTimeHours(336);
    }
  };

  const handleSearch = (query) => {
    console.log('[MapView] Search/filter by:', query);
    skipAutoZoomRef.current = false;
    setSearchQuery(query);
    if (!query.trim()) setForceFullPostId(null);

    // Clear ALL filters to show all search results
    if (query.trim()) {
      setMainFilter("All");
      setSubFilter("All");
      setTimeHours(336); // 2 weeks
    }
  };

  // Zoom intelligent: quand on cherche, zoom pour voir tous les résultats (une seule fois)
  useEffect(() => {
    if (!searchQuery || !visiblePosts.length) return;
    if (skipAutoZoomRef.current) {
      skipAutoZoomRef.current = false;
      return;
    }
    if (searchUserMovedRef.current) return;
    if (searchFitQueryRef.current === searchQuery) return;

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

    // Attendre un peu que les markers soient créés
    const timer = setTimeout(() => {
      const postsWithCoords = visiblePosts.filter(p => {
        const lat = typeof p.lat === 'number' ? p.lat : p.latitude;
        const lon = typeof p.lon === 'number' ? p.lon : p.lng;
        return lat != null && lon != null;
      });

      if (postsWithCoords.length === 0) return;

      if (postsWithCoords.length === 1) {
        // Un seul résultat: zoom dessus
        const p = postsWithCoords[0];
        const lat = typeof p.lat === 'number' ? p.lat : p.latitude;
        const lon = typeof p.lon === 'number' ? p.lon : p.lng;
        map.flyTo({
          center: [lon, lat],
          zoom: 14,
          speed: 1.2,
          essential: true
        });
      } else {
        // Plusieurs résultats: calculer bounds et fitter
        const lats = postsWithCoords.map(p => typeof p.lat === 'number' ? p.lat : p.latitude);
        const lons = postsWithCoords.map(p => typeof p.lon === 'number' ? p.lon : p.lng);

        const minLat = Math.min(...lats);
        const maxLat = Math.max(...lats);
        const minLon = Math.min(...lons);
        const maxLon = Math.max(...lons);

        map.fitBounds(
          [[minLon, minLat], [maxLon, maxLat]],
          {
            padding: { top: 100, bottom: 100, left: 100, right: 100 },
            maxZoom: 15,
            duration: 1000
          }
        );
        searchFitQueryRef.current = searchQuery;
      }
    }, 300); // Délai pour laisser les markers se créer

    return () => clearTimeout(timer);
  }, [searchQuery, visiblePosts, mapRef]);

  useEffect(() => {
    searchFitQueryRef.current = "";
    searchUserMovedRef.current = false;
  }, [searchQuery]);

  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (!searchQuery) return;
    const handleMove = () => {
      searchUserMovedRef.current = true;
    };
    map.on("movestart", handleMove);
    return () => map.off("movestart", handleMove);
  }, [mapRef, searchQuery]);

  const handleProfileVisitIsland = (island) => {
    console.log('[MapView] Visiting island from profile:', island.username);
    setSelectedProfile(null); // Close profile modal
    onSelectIsland && onSelectIsland(island); // Open island viewer via App.jsx
  };

  // Global click handler for author links
  useEffect(() => {
    const handleAuthorClick = (e) => {
      const authorLink = e.target.closest('.sw-author-link');
      if (authorLink) {
        const author = authorLink.getAttribute('data-author');
        if (author) {
          console.log('[MapView] Author clicked:', author);
          setSelectedProfile(author);
        }
      }
    };

    document.addEventListener('click', handleAuthorClick);
    return () => document.removeEventListener('click', handleAuthorClick);
  }, []);

  const closeCreateModal = () => {
    setIsCreating(false);
    setModeSelectorVisible(false);
    setModeChosen(false);
  };

  const handleModeSelect = (modeId) => {
    setContentMode(modeId);
    setModeSelectorVisible(false);
    setModeChosen(true);
  };

  const handlePrevStep = () => {
    setCreateStep((prev) => Math.max(0, prev - 1));
  };

  const handleOpenCreate = () => {
    if (!authenticated) {
      try {
        window.dispatchEvent(
          new CustomEvent("sociowire:auth-required", {
            detail: {
              message: "Please sign in or create a free account to create a post.",
              mode: "login",
            },
          })
        );
      } catch {}
      return;
    }

    if (needsEmailVerification) {
      try {
        window.dispatchEvent(
          new CustomEvent("sociowire:auth-required", {
            detail: {
              message:
                "Please verify your email before creating a post. Check your inbox or resend verification in the top bar.",
              mode: "login",
            },
          })
        );
      } catch {}
      return;
    }

    setIsCreating(true);
    setCreateStep(0);
    setDraftTitle("");
    setDraftBody("");
    setDraftUrl("");
    setDraftSource("");
    setDraftPublishedAt("");
    setDraftImage("");
    setDraftImageSmall("");
    setDraftImageMedium("");
    setDraftImageLarge("");
    setDraftImageFile(null);
    setDraftImagePreview(LINK_PREVIEW_PLACEHOLDER);
    setDraftMediaUrls([]);
    setUseCustomImage(false);
    setPostVisibility("public");
    setPostGroupId("");
    setPostUsers("");
    setImageVisibility("public");
    setImageGroupId("");
    setImageUsers("");
    setUrlPreviewLoading(false);
    setUrlPreviewError("");
    setTitleTouched(false);
    setBodyTouched(false);
    setImageTouched(false);
    lastPreviewUrlRef.current = "";
    const main = mainFilter === "All" ? "News" : mainFilter;
    setDraftCategory(main);
    const list = CREATE_CATEGORY_MAP[main] || CREATE_CATEGORY_MAP.News;
    setDraftSubCategory(list[0]);
    setDraftCoords(null);
    setModeSelectorVisible(true);
    setModeChosen(false);
  };

  // Update ref for long-press callback
  useEffect(() => {
    longPressCallbackRef.current = handleOpenCreate;
  }, [authenticated, needsEmailVerification, mainFilter]);

  useEffect(() => {
    setUseCustomImage(contentMode !== "link");
  }, [contentMode]);

  const handleSearchSubmit = (e) => {
    e.preventDefault();
  };

  const handleClearSearch = () => {
    setSearchQuery("");
  };

  const updateImageVariantsFromGallery = (urls) => {
    const cleaned = (urls || []).filter(Boolean);
    const small = cleaned[0] || "";
    const medium = cleaned[1] || small;
    const large = cleaned[2] || medium || small;
    setDraftImageSmall(small);
    setDraftImageMedium(medium);
    setDraftImageLarge(large);
    setDraftImage(small);
    setDraftImagePreview(small);
  };

  const addMediaUrlToGallery = (rawUrl) => {
    const trimmed = (rawUrl || "").trim();
    if (!trimmed) return false;
    setDraftMediaUrls((prev) => {
      if (prev.includes(trimmed)) {
        updateImageVariantsFromGallery(prev);
        return prev;
      }
      const next = [...prev, trimmed];
      updateImageVariantsFromGallery(next);
      return next;
    });
    setUseCustomImage(true);
    return true;
  };

  const removeMediaUrlAtIndex = (index) => {
    setDraftMediaUrls((prev) => {
      if (index < 0 || index >= prev.length) return prev;
      const next = prev.filter((_, idx) => idx !== index);
      updateImageVariantsFromGallery(next);
      if (next.length === 0) {
        setDraftImage("");
        setDraftImagePreview("");
      } else {
        setDraftImage(next[0]);
        setDraftImagePreview(next[0]);
      }
      return next;
    });
  };

  const replaceMediaUrl = (from, to) => {
    if (!from || !to || from === to) return;
    setDraftMediaUrls((prev) => {
      let replaced = false;
      const next = prev.map((value) => {
        if (value === from) {
          replaced = true;
          return to;
        }
        return value;
      });
      if (!replaced) next.push(to);
      updateImageVariantsFromGallery(next);
      return next;
    });
  };

  const readFileAsDataURL = (file) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => {
        resolve(reader.result || "");
      };
      reader.onerror = reject;
      reader.readAsDataURL(file);
    });
  };

  const handleAddCustomImageUrl = () => {
    const raw = (draftImage || "").trim();
    if (!raw) {
      setSaveError("Paste an image URL first.");
      return;
    }
    if (!/^https?:\/\//i.test(raw)) {
      setSaveError("Image URL must start with http:// or https://");
      return;
    }
    const added = addMediaUrlToGallery(raw);
    if (added) {
      setDraftImage("");
      setSaveError("");
    }
  };

  const handleImageFileChange = async (e) => {
    const files = Array.from(e.target.files || []);
    if (!files.length) return;
    setImageTouched(true);
    setSaveError("");

    setUploadingImage(true);
    for (const file of files) {
      if (!file.type.startsWith("image/")) {
        setSaveError("Please select an image file");
        continue;
      }
      if (file.size > 5 * 1024 * 1024) {
        setSaveError("Image must be less than 5MB");
        continue;
      }

      setDraftImageFile(file);
      let placeholder = "";
      try {
        const dataUrl = await readFileAsDataURL(file);
        if (typeof dataUrl === "string" && dataUrl) {
          placeholder = dataUrl;
          setDraftImagePreview(dataUrl);
          addMediaUrlToGallery(dataUrl);
        }
      } catch (err) {
        console.error("File read error:", err);
        setSaveError("Failed to read image");
      }

      try {
        const formData = new FormData();
        formData.append("file", file);
        formData.append("visibility", imageVisibility);
        if (imageGroupId.trim()) formData.append("group_id", imageGroupId.trim());
        if (imageUsers.trim()) formData.append("users", imageUsers.trim());

        const res = await fetch("/api/assets/upload", {
          method: "POST",
          body: formData,
          credentials: "include",
          headers: token ? { Authorization: `Bearer ${token}` } : {},
        });

        if (!res.ok) {
          throw new Error("Upload failed");
        }

        const data = await res.json();
        const variants = data && data.variants ? data.variants : null;
        const pickRef = (v) => {
          if (!v) return "";
          if (v.url) return v.url;
          if (v.asset_id) return `asset://${v.asset_id}`;
          return "";
        };
        const smallRef = variants ? pickRef(variants.small) : "";
        const mediumRef = variants ? pickRef(variants.medium) : "";
        let largeRef = variants ? pickRef(variants.large) : "";
        if (!largeRef) {
          if (data && data.url) largeRef = data.url;
          else if (data && data.asset_id) largeRef = `asset://${data.asset_id}`;
        }
        const fallbackRef = largeRef || mediumRef || smallRef;
        const selected = fallbackRef || mediumRef || smallRef;
        if (selected) {
          const resolved = await resolveAssetRef(selected);
          const finalUrl = resolved || selected;
          replaceMediaUrl(placeholder || selected, finalUrl);
          setDraftImagePreview(finalUrl);
        }
      } catch (err) {
        console.error("Image upload error:", err);
        setSaveError("Failed to upload image");
      }
    }
    setUploadingImage(false);
    e.target.value = "";
  };

  const handleUrlPreview = async (rawOverride) => {
    const raw = (rawOverride || draftUrl).trim();
    if (!raw || urlPreviewLoading) return;
    if (!/^https?:\/\//i.test(raw)) return;
    if (raw === lastPreviewUrlRef.current) return;
    setUrlPreviewLoading(true);
    setUrlPreviewError("");
    lastPreviewUrlRef.current = raw;

    try {
      const data = await fetchPostPreview(raw, token);
      if (!data) throw new Error("Preview unavailable");

      const title = (data.title || "").trim();
      const desc = (data.description || "").trim();
      const image = (data.image || "").trim();
      const isPlaceholder =
        data.image_placeholder ||
        (image && image === LINK_PREVIEW_PLACEHOLDER);

      if (!titleTouched && title) setDraftTitle(title);
      if (!bodyTouched && desc) setDraftBody(desc);

      if (data.source) setDraftSource(String(data.source));
      if (data.published_at) setDraftPublishedAt(String(data.published_at));

      if (!imageTouched) {
        if (image && !isPlaceholder) {
          setUseCustomImage(true);
          if (!draftImageFile && (!draftImagePreview || draftImagePreview === LINK_PREVIEW_PLACEHOLDER)) {
            addMediaUrlToGallery(image);
          }
        } else {
          setUseCustomImage(false);
          setDraftImagePreview(LINK_PREVIEW_PLACEHOLDER);
        }
      }
    } catch (err) {
      setUrlPreviewError(err?.message || "Failed to fetch link preview");
    } finally {
      setUrlPreviewLoading(false);
    }
  };

  useEffect(() => {
    const raw = draftUrl.trim();
    if (!raw || !/^https?:\/\//i.test(raw)) return;
    if (raw === lastPreviewUrlRef.current) return;
    if (previewTimerRef.current) clearTimeout(previewTimerRef.current);
    previewTimerRef.current = setTimeout(() => {
      handleUrlPreview(raw);
    }, 650);
    return () => {
      if (previewTimerRef.current) clearTimeout(previewTimerRef.current);
    };
  }, [draftUrl]);

  const handleSubmitPost = async () => {
    if (isSaving) return;
    setSaveError("");

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

    if (!authenticated) {
      setSaveError("You must be logged in to post.");
      return;
    }

    if (needsEmailVerification) {
      setSaveError(
        "Verify your email to post. Use 'Resend email' in the top bar, then login again."
      );
      return;
    }

    const authorName = (username || "").trim() || "anon";

    let lng;
    let lat;

    if (draftCoords && draftCoords.length === 2) {
      lng = draftCoords[0];
      lat = draftCoords[1];
    } else {
      const stage = stageRef.current;
      const crosshairEl = stage ? stage.querySelector(".crosshair-aim") : null;
      const containerEl = map.getContainer();
      if (crosshairEl && containerEl) {
        const crossRect = crosshairEl.getBoundingClientRect();
        const containerRect = containerEl.getBoundingClientRect();
        const px = crossRect.left + crossRect.width / 2 - containerRect.left;
        const py = crossRect.top + crossRect.height / 2 - containerRect.top;
        const correctedLngLat = map.unproject([px, py]);
        lng = correctedLngLat.lng;
        lat = correctedLngLat.lat;
      } else {
        const center = map.getCenter();
        lng = center.lng;
        lat = center.lat;
      }
    }

    if (!draftTitle.trim()) {
      setSaveError("Title required.");
      return;
    }

    try {
      setIsSaving(true);

      const payload = {
        title: draftTitle.trim(),
        snippet: draftBody.trim() || draftTitle.trim(),
        category:
          draftCategory === "Events" ? "EVENT" : draftCategory.toUpperCase(),
        sub_category: draftSubCategory || "ALL",
        lat,
        lon: lng,
        author: authorName,
        visibility: postVisibility,
        group_id: postGroupId.trim(),
        users: postUsers
          .split(",")
          .map((u) => u.trim())
          .filter(Boolean),
        // Mark as user-created post so enrichment preserves the position
        source: "sociowire",
      };

      if (draftUrl.trim()) {
        payload.url = draftUrl.trim();
      }
      if (draftSource.trim()) {
        payload.source = draftSource.trim();
      }
      if (draftPublishedAt.trim()) {
        payload.published_at = draftPublishedAt.trim();
      }

      // Filter out data: URLs - they crash browsers and bloat the DB
      const mediaCandidates = draftMediaUrls.filter(
        (url) => url && !url.startsWith("data:")
      );
      if (mediaCandidates.length > 0) {
        payload.image_small = mediaCandidates[0];
        payload.image_medium = mediaCandidates[1] || mediaCandidates[0];
        payload.image_large =
          mediaCandidates[2] || mediaCandidates[1] || mediaCandidates[0];
        payload.media_urls = mediaCandidates;
      } else if (useCustomImage && draftImage.trim() && !draftImage.trim().startsWith("data:")) {
        const fallback = draftImage.trim();
        payload.image_small = fallback;
        payload.image_medium = fallback;
        payload.image_large = fallback;
      }

      const newPost = await createPost(payload, token);

      if (newPost && newPost.id) handleIncomingPost(newPost);

      setIsSaving(false);
      setIsCreating(false);
    } catch (e) {
      console.error(e);
      setIsSaving(false);

      const code = e && (e.code || "");
      const msg = String((e && e.message) || "").toLowerCase();
      const isNotVerified =
        code === "email_not_verified" ||
        (e &&
          e.status === 403 &&
          (msg.includes("not verified") || msg.includes("verify")));

      if (isNotVerified) {
        setSaveError(
          "Verify your email to post. Use 'Resend email' in the top bar, then login again."
        );
      } else {
        setSaveError((e && e.message) || "Error creating post.");
      }
    }
  };

  const handleMainFilterSelect = (code) => {
    if (!FILTER_MAIN_CATEGORIES.includes(code)) return;
    setMainFilter(code);
  };

  const handleSelectPost = (post) => {
    if (onSelectPost) onSelectPost(post);
    const map = mapRef.current;
    if (!map) return;
    const lng = post.lon ?? post.lng;
    const lat = post.lat ?? post.latitude;
    if (typeof lng === "number" && typeof lat === "number") {
      map.flyTo({ center: [lng, lat], zoom: 8, speed: 1.4 });
    }
  };

  const galleryPreviewImage =
    draftMediaUrls[0] ||
    draftImagePreview ||
    draftImageLarge ||
    draftImageMedium ||
    draftImageSmall ||
    draftImage;
  const galleryItems = draftMediaUrls.filter(
    (url) => url && url !== LINK_PREVIEW_PLACEHOLDER
  );
  const hasCustomImages = galleryItems.length > 0;
  const isLinkMode = contentMode === "link";
  const shouldShowPreviewCard = isLinkMode;
  const galleryThumbnails = shouldShowPreviewCard ? galleryItems.slice(1) : galleryItems;

  return (
    <div className="map-page">
      <div className="map-stage" ref={stageRef}>
        <div ref={containerRef} className="map-container" />
        {status && <div className="map-status">{status}</div>}
        {debugEnabled && (
          <>
            <div
              className="sw-debug-banner"
              style={{
                position: "fixed",
                top: 62,
                left: 8,
                zIndex: 99999,
                padding: "6px 8px",
                background: "rgba(0,0,0,0.8)",
                color: "#fff",
                fontSize: 12,
                borderRadius: 6,
                maxWidth: "92vw",
                pointerEvents: "none",
              }}
            >
              {debugStatus || "debug active"}
            </div>
            <div
              style={{
                position: "fixed",
                bottom: 0,
                left: 0,
                right: 0,
                zIndex: 99999,
                background: "#ffea00",
                color: "#111",
                fontWeight: 700,
                fontSize: 14,
                textAlign: "center",
                padding: "4px 0",
                letterSpacing: "0.08em",
                pointerEvents: "none",
              }}
            >
              DEV DEBUG
            </div>
          </>
        )}

        {/* TOP: Search avec suggestions intelligentes */}
        <div className="map-overlay map-overlay-top">
          <div className="sw-top-row">
            <div className="sw-search-row">
              <SmartSearchBar
                placeholder="Search cities, places, posts..."
                onPick={handlePickSuggestion}
                onSearch={handleSearch}
                onMySpot={handleFlyToMe}
                onPlaceTourWire={handleOpenCreate}
              />
              <div className="sw-search__side" aria-label="Search actions">
                {CLUSTERS_ENABLED && (
                  <button
                    type="button"
                    className={
                      "sw-search__iconBtn" + (mainNewsOnly ? " is-active" : "")
                    }
                    title="Main news"
                    onClick={() => setMainNewsOnly((prev) => !prev)}
                  >
                    <i className="fa-solid fa-bolt"></i>
                  </button>
                )}
                <button
                  type="button"
                  className="sw-search__iconBtn"
                  title="Place / Tour / Wire"
                  onClick={() => handleOpenCreate && handleOpenCreate()}
                >
                  <i className="fa-solid fa-plus"></i>
                </button>
                <button
                  type="button"
                  className="sw-search__iconBtn"
                  title="Profile Island"
                  onClick={() => onOpenIslands && onOpenIslands()}
                >
                  <i className="fa-solid fa-umbrella-beach"></i>
                </button>
              </div>
            </div>
          </div>
        </div>

        <div className="map-overlay map-overlay-left">
          <TimeFilterButtons
            activeHours={timeHours}
            onSelect={(hours) => setTimeHours(hours)}
          />
        </div>

        <div className="map-overlay map-overlay-right">
          <FilterButtons active={mainFilter} onSelect={handleMainFilterSelect} />
        </div>

        <div className="map-overlay map-overlay-bottom">
          <div className="sw-bottom-row">
            {bottomCategories.map((c) => (
              <button
                key={c}
                type="button"
                className={
                  "sw-bottom-item" + (subFilter === c ? " sw-bottom-active" : "")
                }
                onClick={() => setSubFilter(c)}
              >
                <div className="sw-bottom-circle">
                  {(() => {
                    const ic = getSubcatIcon(mainFilter, c);
                    return ic ? (
                      <i className={ic} />
                    ) : c === "All" ? (
                      "All"
                    ) : (
                      c.slice(0, 2)
                    );
                  })()}
                </div>
                <div className="sw-bottom-label">{c}</div>
              </button>
            ))}
          </div>
        </div>

        {isCreating && modeChosen && (
          <div className="map-overlay map-crosshair">
            <div className="crosshair-aim" />
          </div>
        )}

        {modeSelectorVisible && (
          <>
            <div className="mode-selector-backdrop" onClick={closeCreateModal} />
            <div className="map-overlay mode-selector-widget mode-selector-v2">
              <button className="mode-selector-close" onClick={closeCreateModal} title="Close">
                <i className="fa-solid fa-xmark" />
              </button>
              <div className="mode-selector-title">Create a post</div>
              <div className="mode-selector-chips">
                {CONTENT_CREATOR_MODES.map((mode) => (
                  <button
                    key={mode.id}
                    className={
                      "mode-selector-chip" +
                      (mode.id === contentMode ? " is-active" : "")
                    }
                    onClick={() => handleModeSelect(mode.id)}
                  >
                    <i className={mode.icon} />
                    <span>{mode.label}</span>
                  </button>
                ))}
              </div>
            </div>
          </>
        )}

        {isCreating && modeChosen && (
          <div className="create-floating-ui">
            {/* Close button */}
            <button className="cfloat-close" onClick={closeCreateModal}>
              <i className="fa-solid fa-xmark" />
            </button>

            {/* Step indicator */}
            <div className="cfloat-box cfloat-step">
              <i className={currentContentMode.icon} />
              <span>Step {createStep + 1}/3</span>
            </div>

            {/* STEP 0 - Position hint */}
            {createStep === 0 && (
              <div className="cfloat-box cfloat-hint">
                <p>{currentFlow.hint}</p>
              </div>
            )}

            {/* STEP 1 - Category */}
            {createStep === 1 && (
              <div className="cfloat-box cfloat-category">
                <label>Category</label>
                <div className="cfloat-selects">
                  <select value={draftCategory} onChange={(e) => {
                    setDraftCategory(e.target.value);
                    setDraftSubCategory((CREATE_CATEGORY_MAP[e.target.value] || CREATE_CATEGORY_MAP.News)[0]);
                  }}>
                    {['News', 'Friends', 'Events', 'Market'].map((c) => <option key={c}>{c}</option>)}
                  </select>
                  <select value={draftSubCategory} onChange={(e) => setDraftSubCategory(e.target.value)}>
                    {(CREATE_CATEGORY_MAP[draftCategory] || CREATE_CATEGORY_MAP.News).map((s) => <option key={s}>{s}</option>)}
                  </select>
                </div>
              </div>
            )}

            {/* STEP 1 - Title */}
            {createStep === 1 && (
              <div className="cfloat-box cfloat-title">
                <label>Title or Link</label>
                <input
                  placeholder={currentFlow.placeholder}
                  value={draftTitle}
                  autoComplete="off"
                  autoCorrect="off"
                  autoCapitalize="off"
                  spellCheck="false"
                  onChange={(e) => {
                    const v = e.target.value;
                    if (isLikelyUrl(v)) {
                      setDraftTitle(v); setTitleTouched(false); setDraftUrl(v.trim());
                      setUrlPreviewError(""); setDraftSource(""); setDraftPublishedAt("");
                      handleUrlPreview(v);
                    } else {
                      setDraftTitle(v); setTitleTouched(true);
                    }
                  }}
                  onPaste={(e) => {
                    const pasted = e.clipboardData?.getData('text') || '';
                    if (pasted && isLikelyUrl(pasted.trim())) {
                      e.preventDefault();
                      const v = pasted.trim();
                      setDraftTitle(v); setTitleTouched(false); setDraftUrl(v);
                      setUrlPreviewError(""); setDraftSource(""); setDraftPublishedAt("");
                      handleUrlPreview(v);
                    }
                  }}
                />
                {draftUrl && !draftSource && <span className="cfloat-meta">Fetching preview...</span>}
                {draftSource && <span className="cfloat-meta">{draftSource}</span>}
                {urlPreviewError && <span className="cfloat-error">{urlPreviewError}</span>}
              </div>
            )}

            {/* STEP 2 - Preview */}
            {createStep === 2 && shouldShowPreviewCard && (
              <div className="cfloat-box cfloat-preview">
                <img src={galleryPreviewImage || LINK_PREVIEW_PLACEHOLDER} alt="Preview" />
                {draftSource && <span>{draftSource}</span>}
              </div>
            )}

            {/* STEP 2 - Description */}
            {createStep === 2 && (
              <div className="cfloat-box cfloat-desc">
                <label>Description</label>
                <textarea
                  rows={3}
                  placeholder={currentFlow.bodyPlaceholder}
                  value={draftBody}
                  onChange={(e) => { setDraftBody(e.target.value); setBodyTouched(true); }}
                />
              </div>
            )}

            {/* STEP 2 - Image */}
            {createStep === 2 && (
              <div className="cfloat-box cfloat-image">
                <div className="cfloat-img-toggle">
                  <button className={!useCustomImage ? "active" : ""} onClick={() => setUseCustomImage(false)}>
                    <i className="fa-solid fa-icons" /> Icon
                  </button>
                  <button className={useCustomImage ? "active" : ""} onClick={() => setUseCustomImage(true)}>
                    <i className="fa-solid fa-image" /> Image
                  </button>
                </div>
                {useCustomImage && (
                  <>
                    <div className="cfloat-img-upload">
                      <label>
                        <i className="fa-solid fa-upload" /> Upload
                        <input type="file" accept="image/*" multiple onChange={handleImageFileChange} disabled={uploadingImage} hidden />
                      </label>
                      <label>
                        <i className="fa-solid fa-camera" /> Camera
                        <input type="file" accept="image/*" capture="environment" onChange={handleImageFileChange} disabled={uploadingImage} hidden />
                      </label>
                      {uploadingImage && <span>Uploading...</span>}
                    </div>
                    {galleryItems.length > 0 && (
                      <div className="cfloat-gallery">
                        {galleryItems.map((url, idx) => (
                          <div key={idx} className="cfloat-gallery-item">
                            <img src={url} alt={`Upload ${idx + 1}`} />
                            <button onClick={() => removeMediaUrlAtIndex(idx)} title="Remove">
                              <i className="fa-solid fa-xmark" />
                            </button>
                          </div>
                        ))}
                      </div>
                    )}
                  </>
                )}
              </div>
            )}

            {/* Error display */}
            {saveError && (
              <div className="cfloat-box cfloat-error-box">
                <i className="fa-solid fa-exclamation-triangle" />
                <span>{saveError}</span>
              </div>
            )}

            {/* Navigation buttons */}
            <div className="cfloat-box cfloat-nav">
              <button className="cfloat-btn" onClick={handlePrevStep} disabled={createStep === 0}>
                <i className="fa-solid fa-arrow-left" /> Back
              </button>
              <button
                className="cfloat-btn cfloat-btn-primary"
                disabled={isSaving}
                onClick={() => {
                  if (contentMode === "live" && createStep === 0) {
                    const map = mapRef.current;
                    if (map) {
                      const stage = stageRef.current;
                      const crosshairEl = stage?.querySelector(".crosshair-aim");
                      const containerEl = map.getContainer();
                      let coords;
                      if (crosshairEl && containerEl) {
                        const cr = crosshairEl.getBoundingClientRect();
                        const co = containerEl.getBoundingClientRect();
                        const ll = map.unproject([cr.left + cr.width/2 - co.left, cr.top + cr.height/2 - co.top]);
                        coords = [ll.lng, ll.lat];
                      } else {
                        const c = map.getCenter();
                        coords = [c.lng, c.lat];
                      }
                      setLiveCoords(coords);
                      setShowLiveBroadcast(true);
                      setIsCreating(false);
                      setModeChosen(false);
                    }
                    return;
                  }
                  if (createStep < 2) setCreateStep((p) => p + 1);
                  else handleSubmitPost();
                }}
              >
                {createStep === 2 ? (isSaving ? "Posting..." : "Post") : (contentMode === "live" && createStep === 0 ? "Go Live" : "Next")}
                <i className="fa-solid fa-arrow-right" />
              </button>
            </div>
          </div>
        )}

      </div>

      {/* User Profile Modal */}
      {selectedProfile && (
        <UserProfile
          username={selectedProfile}
          onClose={() => setSelectedProfile(null)}
          onVisitIsland={handleProfileVisitIsland}
        />
      )}

      {/* Live Broadcast Modal - Using LiveKit */}
      {showLiveBroadcast && (
        <LiveKitBroadcast
          coords={liveCoords}
          onClose={() => {
            setShowLiveBroadcast(false);
            setLiveCoords(null);
          }}
          onStreamCreated={({ roomName, title, coords }) => {
            console.log("[MapView] LiveKit stream created:", roomName, title, coords);
          }}
        />
      )}

      {activeLivePost && (
        <LiveKitViewerModal
          post={activeLivePost}
          onClose={() => setActiveLivePost(null)}
        />
      )}
    </div>
  );
}
