import { useEffect, useRef, useCallback } from "react";
import { fetchEventClusters } from "../../api/client";

const SOURCE_ID = "sw-event-clusters";
const FILL_LAYER_ID = "sw-event-cluster-fill";
const LINE_LAYER_ID = "sw-event-cluster-line";
const LABEL_LAYER_ID = "sw-event-cluster-label";
const CENTER_SOURCE_ID = "sw-event-cluster-centers";

// Cross product of vectors OA and OB (O is origin)
function cross(o, a, b) {
  return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
}

// Convex hull using Andrew's monotone chain algorithm
function convexHull(points) {
  if (points.length < 3) return points;

  // Sort by x, then by y
  const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);

  // Build lower hull
  const lower = [];
  for (const p of sorted) {
    while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
      lower.pop();
    }
    lower.push(p);
  }

  // Build upper hull
  const upper = [];
  for (let i = sorted.length - 1; i >= 0; i--) {
    const p = sorted[i];
    while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
      upper.pop();
    }
    upper.push(p);
  }

  // Remove last point of each half (it's repeated)
  lower.pop();
  upper.pop();

  return lower.concat(upper);
}

// Add buffer/padding around hull points and smooth corners
function bufferHull(hull, padding) {
  if (hull.length < 3) return hull;

  const result = [];
  const n = hull.length;

  for (let i = 0; i < n; i++) {
    const curr = hull[i];
    const prev = hull[(i - 1 + n) % n];
    const next = hull[(i + 1) % n];

    // Direction vectors
    const dx1 = curr[0] - prev[0];
    const dy1 = curr[1] - prev[1];
    const dx2 = next[0] - curr[0];
    const dy2 = next[1] - curr[1];

    // Normalize and get perpendicular (outward)
    const len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1) || 1;
    const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;

    // Outward normal (average of the two edge normals)
    const nx = (-dy1 / len1 - dy2 / len2) / 2;
    const ny = (dx1 / len1 + dx2 / len2) / 2;
    const nlen = Math.sqrt(nx * nx + ny * ny) || 1;

    // Push vertex outward
    const buffered = [
      curr[0] + (nx / nlen) * padding,
      curr[1] + (ny / nlen) * padding
    ];

    result.push(buffered);
  }

  return result;
}

// Smooth polygon by adding midpoints (Chaikin's algorithm - 1 iteration)
function smoothPolygon(points) {
  if (points.length < 3) return points;

  const result = [];
  const n = points.length;

  for (let i = 0; i < n; i++) {
    const curr = points[i];
    const next = points[(i + 1) % n];

    // Add point at 25% and 75% along each edge
    result.push([
      curr[0] * 0.75 + next[0] * 0.25,
      curr[1] * 0.75 + next[1] * 0.25
    ]);
    result.push([
      curr[0] * 0.25 + next[0] * 0.75,
      curr[1] * 0.25 + next[1] * 0.75
    ]);
  }

  return result;
}

// Create organic "lake" shape from cluster member posts
function clusterToPolygon(cluster) {
  const { id, title, post_count, member_posts, bounding_box } = cluster;

  // Get valid points from member posts
  let points = [];
  if (member_posts && member_posts.length > 0) {
    points = member_posts
      .filter(p => p.lat && p.lon && p.lat !== 0 && p.lon !== 0)
      .map(p => [p.lon, p.lat]);
  }

  // Fallback to bounding box center if no points
  if (points.length === 0 && bounding_box) {
    const { min_lat, max_lat, min_lon, max_lon } = bounding_box;
    const centerLon = (min_lon + max_lon) / 2;
    const centerLat = (min_lat + max_lat) / 2;
    const radius = Math.max((max_lon - min_lon), (max_lat - min_lat)) / 2 || 0.02;

    // Create a circle
    for (let i = 0; i < 12; i++) {
      const angle = (i / 12) * Math.PI * 2;
      points.push([
        centerLon + Math.cos(angle) * radius,
        centerLat + Math.sin(angle) * radius * 0.7 // Squish for lat/lon ratio
      ]);
    }
  }

  if (points.length < 3) {
    // Not enough points - create a small circle around cluster center
    const centerLon = cluster.lon || (bounding_box ? (bounding_box.min_lon + bounding_box.max_lon) / 2 : 0);
    const centerLat = cluster.lat || (bounding_box ? (bounding_box.min_lat + bounding_box.max_lat) / 2 : 0);
    const radius = 0.02;

    points = [];
    for (let i = 0; i < 12; i++) {
      const angle = (i / 12) * Math.PI * 2;
      points.push([
        centerLon + Math.cos(angle) * radius,
        centerLat + Math.sin(angle) * radius * 0.7
      ]);
    }
  }

  // Compute convex hull
  let hull = convexHull(points);

  // Calculate padding based on spread of points
  const lons = points.map(p => p[0]);
  const lats = points.map(p => p[1]);
  const lonSpread = Math.max(...lons) - Math.min(...lons);
  const latSpread = Math.max(...lats) - Math.min(...lats);
  const padding = Math.max(lonSpread, latSpread) * 0.15 + 0.005; // 15% + minimum

  // Buffer the hull outward
  hull = bufferHull(hull, padding);

  // Smooth the polygon twice for organic look
  hull = smoothPolygon(hull);
  hull = smoothPolygon(hull);

  // Close the ring
  if (hull.length > 0) {
    hull.push(hull[0]);
  }

  return {
    type: "Feature",
    id,
    properties: {
      id,
      title,
      post_count,
    },
    geometry: {
      type: "Polygon",
      coordinates: [hull],
    },
  };
}

// Create center points for labels
function clusterToPoint(cluster) {
  return {
    type: "Feature",
    properties: {
      id: cluster.id,
      title: cluster.title?.slice(0, 35) + (cluster.title?.length > 35 ? "..." : "") || "Cluster",
      post_count: cluster.post_count,
    },
    geometry: {
      type: "Point",
      coordinates: [cluster.lon, cluster.lat],
    },
  };
}

/**
 * EventClustersLayer - Renders news cluster impact zones on the map
 */
export default function EventClustersLayer({ map, enabled = true, onClusterClick, onClusterPostIds }) {
  const clustersRef = useRef([]);
  const initializedRef = useRef(false);
  const fetchTimeoutRef = useRef(null);
  const lastBoundsRef = useRef(null);

  // Update GeoJSON data in existing sources
  const updateData = useCallback(() => {
    if (!map) return;
    try {
      const polygons = clustersRef.current
        .filter(c => c.bounding_box || (c.member_posts && c.member_posts.length > 0))
        .map(c => clusterToPolygon(c));

      const points = clustersRef.current.map(clusterToPoint);

      const polygonSource = map.getSource(SOURCE_ID);
      if (polygonSource) {
        polygonSource.setData({ type: "FeatureCollection", features: polygons });
      }

      const pointSource = map.getSource(CENTER_SOURCE_ID);
      if (pointSource) {
        pointSource.setData({ type: "FeatureCollection", features: points });
      }
    } catch (err) {
      console.warn("EventClustersLayer updateData error:", err);
    }
  }, [map]);

  // Fetch clusters from API
  const doFetch = useCallback(async () => {
    if (!map) return;
    try {
      const bounds = map.getBounds();
      // Use less precision to avoid too frequent fetches, but still catch significant moves
      const boundsKey = `${bounds.getWest().toFixed(0)},${bounds.getSouth().toFixed(0)},${bounds.getEast().toFixed(0)},${bounds.getNorth().toFixed(0)}`;

      if (lastBoundsRef.current === boundsKey) return;
      lastBoundsRef.current = boundsKey;

      
      const data = await fetchEventClusters({
        minLat: bounds.getSouth(),
        maxLat: bounds.getNorth(),
        minLon: bounds.getWest(),
        maxLon: bounds.getEast(),
        minPosts: 2,
        limit: 40,
      });

      clustersRef.current = data.clusters || [];
      updateData();

      // Report post IDs to parent
      if (onClusterPostIds) {
        const postIds = new Set();
        for (const cluster of clustersRef.current) {
          if (cluster.member_posts) {
            for (const post of cluster.member_posts) {
              postIds.add(post.id);
            }
          }
        }
        onClusterPostIds(postIds);
      }
    } catch (err) {
      console.warn("EventClustersLayer fetch error:", err);
    }
  }, [map, updateData, onClusterPostIds]);

  // Debounced fetch
  const scheduleFetch = useCallback(() => {
    if (fetchTimeoutRef.current) clearTimeout(fetchTimeoutRef.current);
    fetchTimeoutRef.current = setTimeout(doFetch, 400);
  }, [doFetch]);

  // Initialize sources and layers once
  useEffect(() => {
    if (!map) return;

    const init = () => {
      if (initializedRef.current) return;

      // Check if style is loaded
      try {
        if (!map.isStyleLoaded()) {
          // Retry in 100ms
          setTimeout(init, 100);
          return;
        }
      } catch {
        setTimeout(init, 100);
        return;
      }

      if (map.getSource(SOURCE_ID)) {
        initializedRef.current = true;
        doFetch();
        map.on("moveend", scheduleFetch);
        return;
      }

      try {
        // Sources
        map.addSource(SOURCE_ID, {
          type: "geojson",
          data: { type: "FeatureCollection", features: [] },
        });
        map.addSource(CENTER_SOURCE_ID, {
          type: "geojson",
          data: { type: "FeatureCollection", features: [] },
        });

        // Fill layer - semi-transparent lake-like appearance (start hidden)
        map.addLayer({
          id: FILL_LAYER_ID,
          type: "fill",
          source: SOURCE_ID,
          layout: {
            "visibility": "none",
          },
          paint: {
            "fill-color": [
              "interpolate", ["linear"], ["get", "post_count"],
              2, "#60a5fa",   // lighter blue
              5, "#a78bfa",   // softer purple
              10, "#f472b6",  // softer pink
            ],
            "fill-opacity": 0.35,
          },
        });

        // Border line - subtle glow effect (start hidden)
        map.addLayer({
          id: LINE_LAYER_ID,
          type: "line",
          source: SOURCE_ID,
          layout: {
            "visibility": "none",
          },
          paint: {
            "line-color": [
              "interpolate", ["linear"], ["get", "post_count"],
              2, "#93c5fd",   // even lighter for border
              5, "#c4b5fd",
              10, "#f9a8d4",
            ],
            "line-width": 2,
            "line-blur": 1,
          },
        });

        // Labels (start hidden)
        map.addLayer({
          id: LABEL_LAYER_ID,
          type: "symbol",
          source: CENTER_SOURCE_ID,
          layout: {
            "visibility": "none",
            "text-field": ["concat", ["get", "title"], " (", ["to-string", ["get", "post_count"]], ")"],
            "text-size": 14,
            "text-font": ["Open Sans Bold", "Arial Unicode MS Bold"],
            "text-anchor": "center",
            "text-max-width": 15,
            "text-allow-overlap": false,
            "text-ignore-placement": false,
          },
          paint: {
            "text-color": "#ffffff",
            "text-halo-color": "#1e1b4b",
            "text-halo-width": 2,
            "text-halo-blur": 1,
          },
          minzoom: 3,
        });

        // Click handler
        map.on("click", FILL_LAYER_ID, (e) => {
          if (e.features?.length && onClusterClick) {
            const id = e.features[0].properties?.id;
            const cluster = clustersRef.current.find(c => c.id === id);
            if (cluster) onClusterClick(cluster);
          }
        });

        map.on("mouseenter", FILL_LAYER_ID, () => {
          map.getCanvas().style.cursor = "pointer";
        });
        map.on("mouseleave", FILL_LAYER_ID, () => {
          map.getCanvas().style.cursor = "";
        });

        initializedRef.current = true;

        // Initial fetch only if enabled
        if (enabled) {
          doFetch();
          // Show layers
          map.setLayoutProperty(FILL_LAYER_ID, "visibility", "visible");
          map.setLayoutProperty(LINE_LAYER_ID, "visibility", "visible");
          map.setLayoutProperty(LABEL_LAYER_ID, "visibility", "visible");
        }

        // Fetch on map move
        map.on("moveend", scheduleFetch);
      } catch (err) {
        console.warn("EventClustersLayer init error:", err);
      }
    };

    // Start init - it will retry if style not loaded yet
    init();

    return () => {
      if (fetchTimeoutRef.current) clearTimeout(fetchTimeoutRef.current);
    };
  }, [map, doFetch, scheduleFetch, onClusterClick, enabled]);

  // Toggle visibility
  useEffect(() => {
    if (!map || !initializedRef.current) return;
    try {
      const vis = enabled ? "visible" : "none";
      if (map.getLayer(FILL_LAYER_ID)) map.setLayoutProperty(FILL_LAYER_ID, "visibility", vis);
      if (map.getLayer(LINE_LAYER_ID)) map.setLayoutProperty(LINE_LAYER_ID, "visibility", vis);
      if (map.getLayer(LABEL_LAYER_ID)) map.setLayoutProperty(LABEL_LAYER_ID, "visibility", vis);

      // Refetch when enabled
      if (enabled) {
        lastBoundsRef.current = null;
        doFetch();
      }
    } catch {}
  }, [map, enabled, doFetch]);

  return null;
}
