/* ================================================
   Map components — Leaflet wrappers
================================================ */

const TILE_URLS = {
  satellite: {
    url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
    attribution: "Tiles © Esri",
    maxZoom: 18,
  },
  illustrated: {
    // Watercolor-like / artistic style via Stamen
    url: "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg",
    attribution: "© Stamen · Stadia",
    maxZoom: 16,
  },
};

window.TILE_URLS = TILE_URLS;

/* A static Leaflet map showing all parcels as polygons */
function ParcelsMap({ parcels, mapStyle = "satellite", activeId, onSelect, center = [48.085, 7.65], zoom = 13 }) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const layersRef = React.useRef({ tile: null, polys: {} });

  // init map once
  React.useEffect(() => {
    if (!containerRef.current || mapRef.current) return;
    const m = L.map(containerRef.current, {
      center,
      zoom,
      zoomControl: true,
      attributionControl: true,
    });
    mapRef.current = m;
    return () => { m.remove(); mapRef.current = null; };
  }, []);

  // switch tile layer
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    if (layersRef.current.tile) m.removeLayer(layersRef.current.tile);
    const cfg = TILE_URLS[mapStyle] || TILE_URLS.satellite;
    const tile = L.tileLayer(cfg.url, { maxZoom: cfg.maxZoom, attribution: cfg.attribution });
    tile.addTo(m);
    layersRef.current.tile = tile;
  }, [mapStyle]);

  // draw polygons
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    // clear existing
    Object.values(layersRef.current.polys).forEach((p) => m.removeLayer(p));
    layersRef.current.polys = {};

    parcels.forEach((p) => {
      const isActive = p.id === activeId;
      const poly = L.polygon(p.koordinaten, {
        color: p.farbe || "#6B1F2E",
        weight: isActive ? 3 : 2,
        fillColor: p.farbe || "#6B1F2E",
        fillOpacity: isActive ? 0.55 : 0.35,
        opacity: 0.95,
      });
      poly.bindTooltip(p.lagebuch, {
        permanent: true,
        direction: "center",
        className: "parcel-label",
      });
      poly.on("click", () => onSelect && onSelect(p));
      poly.addTo(m);
      layersRef.current.polys[p.id] = poly;
    });

    // fit bounds first render
    if (parcels.length && !layersRef.current.didFit) {
      const group = L.featureGroup(Object.values(layersRef.current.polys));
      m.fitBounds(group.getBounds().pad(0.2));
      layersRef.current.didFit = true;
    }
  }, [parcels, activeId]);

  // focus active parcel
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m || !activeId) return;
    const poly = layersRef.current.polys[activeId];
    if (poly) m.fitBounds(poly.getBounds().pad(0.8), { animate: true });
  }, [activeId]);

  return <div className="leaflet-map" ref={containerRef} />;
}

window.ParcelsMap = ParcelsMap;

/* Static single-parcel map (for detail / summary) */
function SingleParcelMap({ parcel, mapStyle = "satellite" }) {
  const containerRef = React.useRef(null);
  React.useEffect(() => {
    if (!containerRef.current) return;
    const cfg = TILE_URLS[mapStyle] || TILE_URLS.satellite;
    const m = L.map(containerRef.current, {
      zoomControl: false,
      attributionControl: false,
      dragging: false,
      scrollWheelZoom: false,
      doubleClickZoom: false,
      boxZoom: false,
      keyboard: false,
      touchZoom: false,
      tap: false,
    });
    L.tileLayer(cfg.url, { maxZoom: cfg.maxZoom, attribution: cfg.attribution }).addTo(m);
    const poly = L.polygon(parcel.koordinaten, {
      color: parcel.farbe || "#6B1F2E",
      weight: 3,
      fillColor: parcel.farbe || "#6B1F2E",
      fillOpacity: 0.5,
    }).addTo(m);
    m.fitBounds(poly.getBounds().pad(0.4));
    return () => m.remove();
  }, [parcel, mapStyle]);
  return <div className="leaflet-map" ref={containerRef} />;
}

window.SingleParcelMap = SingleParcelMap;

/* Non-interactive thumbnail map for list cards.
   Uses a dedicated SVG renderer and forces an initial fitBounds
   AFTER the container has real size — otherwise Leaflet's
   overlay SVG can be 0×0 and polygons won't paint. */
function ThumbnailMap({ parcel, mapStyle = "satellite" }) {
  const containerRef = React.useRef(null);
  React.useEffect(() => {
    if (!containerRef.current) return;
    const el = containerRef.current;
    const cfg = TILE_URLS[mapStyle] || TILE_URLS.satellite;
    const color = parcel.farbe || "#6B1F2E";

    // compute bounds once so we can pass them at init
    const bounds = L.latLngBounds(parcel.koordinaten).pad(0.5);

    const renderer = L.canvas({ padding: 2 });

    const m = L.map(el, {
      zoomControl: false,
      attributionControl: false,
      dragging: false,
      scrollWheelZoom: false,
      doubleClickZoom: false,
      boxZoom: false,
      keyboard: false,
      touchZoom: false,
      tap: false,
      renderer,
    });

    L.tileLayer(cfg.url, { maxZoom: cfg.maxZoom, attribution: cfg.attribution }).addTo(m);

    // halo (cream outline) + filled polygon + vertex dots
    L.polygon(parcel.koordinaten, {
      renderer,
      color: "#F5EFE3",
      weight: 4,
      opacity: 0.9,
      fill: false,
      lineJoin: "round",
    }).addTo(m);
    const poly = L.polygon(parcel.koordinaten, {
      renderer,
      color,
      weight: 2.5,
      fillColor: color,
      fillOpacity: 0.65,
      lineJoin: "round",
    }).addTo(m);
    // (no vertex dots — only polygon outline + fill)

    const fit = () => {
      if (!m._container || !m._container.isConnected) return;
      const r = el.getBoundingClientRect();
      if (r.width < 2 || r.height < 2) return;
      m.invalidateSize(false);
      m.fitBounds(bounds, { animate: false });
    };

    // Initial fit on next frame (after layout)
    requestAnimationFrame(() => requestAnimationFrame(fit));
    // Resize observer: fires when container first gets real size too
    const ro = new ResizeObserver(fit);
    ro.observe(el);

    return () => { ro.disconnect(); m.remove(); };
  }, [parcel.id, mapStyle]);
  return <div className="leaflet-map" ref={containerRef} style={{ pointerEvents: "none" }} />;
}

window.ThumbnailMap = ThumbnailMap;

/* Interactive polygon drawer for the wizard */
function PolygonDrawer({ points, onPointsChange, mapStyle = "satellite", center = [48.085, 7.65] }) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const layersRef = React.useRef({ tile: null, poly: null, line: null, markers: [] });
  const pointsRef = React.useRef(points);
  const prevCountRef = React.useRef(points.length);
  pointsRef.current = points;

  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    if (points.length >= 3 && points.length - prevCountRef.current > 1) {
      m.fitBounds(L.polygon(points).getBounds().pad(0.2), { animate: true });
    }
    prevCountRef.current = points.length;
  }, [points]);

  React.useEffect(() => {
    if (!containerRef.current || mapRef.current) return;
    const m = L.map(containerRef.current, {
      center,
      zoom: 16,
      zoomControl: true,
      attributionControl: true,
    });
    mapRef.current = m;

    m.on("click", (e) => {
      const next = [...pointsRef.current, [e.latlng.lat, e.latlng.lng]];
      onPointsChange(next);
    });

    return () => { m.remove(); mapRef.current = null; };
  }, []);

  // tile layer
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    if (layersRef.current.tile) m.removeLayer(layersRef.current.tile);
    const cfg = TILE_URLS[mapStyle] || TILE_URLS.satellite;
    const tile = L.tileLayer(cfg.url, { maxZoom: cfg.maxZoom, attribution: cfg.attribution });
    tile.addTo(m);
    layersRef.current.tile = tile;
  }, [mapStyle]);

  // redraw polygon / markers
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    const L_ = layersRef.current;
    if (L_.poly) { m.removeLayer(L_.poly); L_.poly = null; }
    if (L_.line) { m.removeLayer(L_.line); L_.line = null; }
    L_.markers.forEach((mk) => m.removeLayer(mk));
    L_.markers = [];

    if (points.length >= 3) {
      L_.poly = L.polygon(points, {
        color: "#6B1F2E",
        weight: 2.5,
        fillColor: "#6B1F2E",
        fillOpacity: 0.3,
        dashArray: "4 3",
      }).addTo(m);
    } else if (points.length === 2) {
      L_.line = L.polyline(points, { color: "#6B1F2E", weight: 2.5, dashArray: "4 3" }).addTo(m);
    }

    points.forEach((pt, idx) => {
      const icon = L.divIcon({
        className: "",
        html: `<div style="width:16px;height:16px;border-radius:50%;background:#6B1F2E;border:2.5px solid #F5EFE3;box-shadow:0 2px 4px rgba(0,0,0,0.4);display:grid;place-items:center;color:#F5EFE3;font-size:9px;font-weight:700;font-family:'JetBrains Mono',monospace">${idx + 1}</div>`,
        iconSize: [16, 16],
        iconAnchor: [8, 8],
      });
      const mk = L.marker(pt, { icon, draggable: true });
      mk.on("dragend", (e) => {
        const ll = e.target.getLatLng();
        const next = pointsRef.current.map((p, i) => i === idx ? [ll.lat, ll.lng] : p);
        onPointsChange(next);
      });
      mk.addTo(m);
      L_.markers.push(mk);
    });
  }, [points]);

  return <div className="leaflet-map" ref={containerRef} />;
}

window.PolygonDrawer = PolygonDrawer;

/* ================================================
   FlurstueckPicker — select ALKIS Flurstücke on the map.
   Fetches features for the current viewport (debounced) and lets
   the user toggle selection by clicking. Parents get the full
   Flurstück objects back via onChange.
================================================ */
function FlurstueckPicker({
  selected,
  onChange,
  mapStyle = "satellite",
  center = [48.085, 7.65],
  onStatus,
  initialOutline,
  flyTo,
}) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const layersRef = React.useRef({
    tile: null,
    available: L.layerGroup(),
    chosen: L.layerGroup(),
    labels: L.layerGroup(),
    reference: null,
  });
  const featureIndex = React.useRef(new Map()); // kennzeichen -> feature obj
  const selectedRef = React.useRef(selected);
  selectedRef.current = selected;

  const [loading, setLoading] = React.useState(false);

  const selectedKeys = React.useMemo(
    () => new Set(selected.map(f => f.kennzeichen)),
    [selected]
  );

  // init map
  React.useEffect(() => {
    if (!containerRef.current || mapRef.current) return;
    const m = L.map(containerRef.current, {
      center,
      zoom: 17,
      zoomControl: true,
      attributionControl: true,
    });
    mapRef.current = m;
    layersRef.current.available.addTo(m);
    layersRef.current.chosen.addTo(m);
    layersRef.current.labels.addTo(m);

    let timer = null;
    const reload = () => {
      clearTimeout(timer);
      timer = setTimeout(() => loadFlurstuecke(), 350);
    };
    m.on("moveend", reload);
    m.on("zoomend", reload);

    // initial fit: if we already have selected, fit them; else if initialOutline, fit it
    if (selectedRef.current.length > 0) {
      const bounds = L.latLngBounds([]);
      selectedRef.current.forEach(f => f.koordinaten.forEach(p => bounds.extend(p)));
      if (bounds.isValid()) m.fitBounds(bounds.pad(0.3), { animate: false });
    } else if (initialOutline && initialOutline.length >= 3) {
      const ref = L.polygon(initialOutline, {
        color: "#6B1F2E", weight: 2, fill: false, dashArray: "4 4", opacity: 0.7,
      }).addTo(m);
      layersRef.current.reference = ref;
      m.fitBounds(ref.getBounds().pad(0.3), { animate: false });
    } else {
      loadFlurstuecke();
    }

    // first load after initial fit
    setTimeout(loadFlurstuecke, 80);

    return () => { clearTimeout(timer); m.remove(); mapRef.current = null; };
  }, []);

  // tile layer
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    if (layersRef.current.tile) m.removeLayer(layersRef.current.tile);
    const cfg = TILE_URLS[mapStyle] || TILE_URLS.satellite;
    const tile = L.tileLayer(cfg.url, { maxZoom: cfg.maxZoom, attribution: cfg.attribution });
    tile.addTo(m);
    layersRef.current.tile = tile;
  }, [mapStyle]);

  async function loadFlurstuecke() {
    const m = mapRef.current;
    if (!m) return;
    if (m.getZoom() < 16) {
      onStatus?.({ loading: false, message: "Bitte weiter hereinzoomen (ab Zoom 16).", count: 0, truncated: false });
      redraw([]);
      return;
    }
    const b = m.getBounds();
    const bbox = `${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`;
    setLoading(true);
    onStatus?.({ loading: true, message: "Flurstücke werden geladen…", count: 0, truncated: false });
    try {
      const r = await fetch(`/api/flurstuecke?bbox=${bbox}&limit=400`, {
        headers: { Accept: "application/json" },
      });
      const j = await r.json();
      if (!r.ok) {
        onStatus?.({ loading: false, message: j.error || "Fehler beim Laden.", count: 0, truncated: false });
        redraw([]);
        return;
      }
      redraw(j.features || []);
      onStatus?.({
        loading: false,
        message: null,
        count: j.returned || 0,
        truncated: !!j.truncated,
      });
    } catch (e) {
      onStatus?.({ loading: false, message: "Netzwerkfehler.", count: 0, truncated: false });
    } finally {
      setLoading(false);
    }
  }

  function redraw(features) {
    const m = mapRef.current;
    if (!m) return;
    featureIndex.current.clear();
    layersRef.current.available.clearLayers();
    layersRef.current.labels.clearLayers();

    const sel = new Set(selectedRef.current.map(f => f.kennzeichen));

    features.forEach(f => {
      featureIndex.current.set(f.kennzeichen, f);
      if (sel.has(f.kennzeichen)) return; // drawn by chosen layer
      const poly = L.polygon(f.koordinaten, {
        color: "#F5EFE3",
        weight: 1,
        fillColor: "#F5EFE3",
        fillOpacity: 0.05,
        interactive: true,
      });
      poly.on("mouseover", () => poly.setStyle({ fillOpacity: 0.25, weight: 1.5 }));
      poly.on("mouseout", () => poly.setStyle({ fillOpacity: 0.05, weight: 1 }));
      poly.on("click", () => toggle(f.kennzeichen));
      poly.bindTooltip(`${f.gemarkung_name || ""} · ${f.flurstueckstext || f.zaehler} · ${fmtNum(f.flaeche_ha)} ha`, {
        sticky: true, direction: "top", className: "flur-tooltip",
      });
      poly.addTo(layersRef.current.available);
    });

    drawChosen();
  }

  function drawChosen() {
    const m = mapRef.current;
    if (!m) return;
    layersRef.current.chosen.clearLayers();
    selectedRef.current.forEach(f => {
      const poly = L.polygon(f.koordinaten, {
        color: "#6B1F2E",
        weight: 2.5,
        fillColor: "#6B1F2E",
        fillOpacity: 0.4,
        interactive: true,
      });
      poly.on("click", () => toggle(f.kennzeichen));
      poly.bindTooltip(`${f.gemarkung_name || ""} · ${f.flurstueckstext || f.zaehler} · ${fmtNum(f.flaeche_ha)} ha · klicken zum Abwählen`, {
        sticky: true, direction: "top", className: "flur-tooltip",
      });
      poly.addTo(layersRef.current.chosen);
    });
  }

  function toggle(kennzeichen) {
    const curr = selectedRef.current;
    const exists = curr.find(f => f.kennzeichen === kennzeichen);
    if (exists) {
      onChange(curr.filter(f => f.kennzeichen !== kennzeichen));
    } else {
      const f = featureIndex.current.get(kennzeichen);
      if (f) onChange([...curr, f]);
    }
  }

  // re-render selected layer when selection changes
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m) return;
    // re-run available filter so selected ones aren't double-drawn
    const feats = Array.from(featureIndex.current.values());
    redraw(feats);
  }, [selectedKeys]);

  // imperative fly-to trigger (PLZ-Suche etc.)
  React.useEffect(() => {
    const m = mapRef.current;
    if (!m || !flyTo || typeof flyTo.lat !== "number" || typeof flyTo.lng !== "number") return;
    m.setView([flyTo.lat, flyTo.lng], flyTo.zoom || 17, { animate: true });
  }, [flyTo && flyTo.ts]);

  return (
    <div style={{ position: "relative", width: "100%", height: "100%" }}>
      <div className="leaflet-map" ref={containerRef} />
      {loading && <div className="flur-loading">Lade Flurstücke…</div>}
    </div>
  );
}

window.FlurstueckPicker = FlurstueckPicker;
