import { memo, useEffect, useMemo, useRef, useState } from "react";
import { geoEqualEarth, geoPath } from "d3-geo";
import { feature } from "topojson-client";

const WORLD_TOPO_URL =
  "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json";

const VIEW = { w: 1100, h: 620 };

// TopoJSON fetched once per session (survives route remounts).
let worldFeaturesCache = null;
let worldFeaturesPromise = null;

/** D3 world map: pan/zoom, `colorFor(numericCode)`, selection + hover callbacks. */
function WorldMap({
  colorFor,
  selectedNumeric = null,
  onCountryEnter,
  onCountryMove,
  onCountryLeave,
  onCountryClick,
}) {
  const [countries, setCountries] = useState(null);
  const [transform, setTransform] = useState({ x: 0, y: 0, k: 1 });
  const dragRef = useRef(null);
  const svgRef = useRef(null);

  useEffect(() => {
    let cancelled = false;
    if (worldFeaturesCache) {
      setCountries(worldFeaturesCache);
      return () => {
        cancelled = true;
      };
    }
    if (!worldFeaturesPromise) {
      worldFeaturesPromise = fetch(WORLD_TOPO_URL)
        .then((r) => r.json())
        .then((topo) => {
          const fc = feature(topo, topo.objects.countries);
          worldFeaturesCache = fc.features;
          return worldFeaturesCache;
        })
        .catch((err) => {
          worldFeaturesPromise = null;
          throw err;
        });
    }
    worldFeaturesPromise.then((features) => {
      if (!cancelled) setCountries(features);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  const pathGen = useMemo(() => {
    const projection = geoEqualEarth()
      .scale(190)
      .translate([VIEW.w / 2, VIEW.h / 2 + 10]);
    return geoPath(projection);
  }, []);

  function onMouseDown(e) {
    dragRef.current = {
      startX: e.clientX,
      startY: e.clientY,
      tx: transform.x,
      ty: transform.y,
    };
  }

  function onMouseMove(e) {
    if (dragRef.current) {
      const dx = e.clientX - dragRef.current.startX;
      const dy = e.clientY - dragRef.current.startY;
      setTransform((t) => ({
        ...t,
        x: dragRef.current.tx + dx,
        y: dragRef.current.ty + dy,
      }));
    }
  }

  function endDrag() {
    dragRef.current = null;
  }

  function onWheel(e) {
    e.preventDefault();
    const delta = -e.deltaY * 0.0015;
    const nextK = Math.max(0.7, Math.min(8, transform.k * (1 + delta)));
    if (nextK === transform.k) return;
    // Zoom toward cursor.
    const rect = svgRef.current.getBoundingClientRect();
    const px = e.clientX - rect.left;
    const py = e.clientY - rect.top;
    const ratio = nextK / transform.k;
    setTransform({
      k: nextK,
      x: px - (px - transform.x) * ratio,
      y: py - (py - transform.y) * ratio,
    });
  }

  return (
    <svg
      ref={svgRef}
      viewBox={`0 0 ${VIEW.w} ${VIEW.h}`}
      preserveAspectRatio="xMidYMid meet"
      style={{ width: "100%", height: "100%", display: "block", cursor: dragRef.current ? "grabbing" : "grab" }}
      onMouseDown={onMouseDown}
      onMouseMove={onMouseMove}
      onMouseUp={endDrag}
      onMouseLeave={() => {
        endDrag();
        onCountryLeave?.();
      }}
      onWheel={onWheel}
    >
      <defs>
        <radialGradient id="mapVignette" cx="50%" cy="50%" r="70%">
          <stop offset="60%" stopColor="rgba(0,0,0,0)" />
          <stop offset="100%" stopColor="rgba(0,0,0,0.55)" />
        </radialGradient>
      </defs>

      <rect width={VIEW.w} height={VIEW.h} fill="var(--bg)" />

      <g transform={`translate(${transform.x},${transform.y}) scale(${transform.k})`}>
        {countries?.map((geo) => {
          const numeric = String(geo.id).padStart(3, "0");
          const fill = colorFor(numeric, geo.properties);
          const isSelected = selectedNumeric === numeric;
          const country = {
            numericCode: numeric,
            name: geo.properties.name,
            properties: geo.properties,
          };
          return (
            <path
              key={numeric + "-" + geo.properties.name}
              d={pathGen(geo)}
              fill={fill}
              stroke={isSelected ? "var(--accent)" : "var(--border)"}
              strokeWidth={isSelected ? 1 / transform.k : 0.4 / transform.k}
              vectorEffect="non-scaling-stroke"
              onMouseEnter={(ev) => onCountryEnter?.(country, ev)}
              onMouseMove={(ev) => onCountryMove?.(ev)}
              onMouseLeave={onCountryLeave}
              onClick={(ev) => onCountryClick?.(country, ev)}
              style={{
                cursor: onCountryClick ? "pointer" : "default",
                transition: "fill 250ms ease",
              }}
            />
          );
        })}
      </g>

      <rect
        width={VIEW.w}
        height={VIEW.h}
        fill="url(#mapVignette)"
        pointerEvents="none"
      />
    </svg>
  );
}

export default memo(WorldMap);
