import { MapContainer, Popup, TileLayer, useMapEvents, WMSTileLayer } from "react-leaflet";
import { mapConfig } from "../config/map-config";
import { Mapset } from "@/shared/types/mapset";
import { getWmsLayerBounds, parseWmsUrl } from "../utils/wms";
import { LatLngBoundsExpression, LatLngLiteral, LeafletMouseEvent } from "leaflet";
import React, { useEffect, useState } from "react";
import { MapLegend } from "./map-legend";
import { PopupAttribute } from "./popup-attribute";

export default function PreviewMap({
  mapset,
  isActiveControl,
  centerCustom,
  onMapClick,
  isShowPopup = false,
  isShowLoadingTileLayer = false,
  isShowLegend = false,
}: Readonly<{
  mapset: Mapset;
  isActiveControl?: boolean;
  centerCustom?: [number, number];
  onMapClick?: (e: LeafletMouseEvent) => void;
  isShowPopup?: boolean;
  isShowLoadingTileLayer?: boolean;
  isShowLegend?: boolean;
}>) {
  const parsed = parseWmsUrl(mapset?.layer_url);
  const [bounds, setBounds] = useState<LatLngBoundsExpression | undefined>(parsed?.params.bounds ?? undefined);

  // Popup content state
  const [popupLoading, setPopupLoading] = useState(false);
  const [popupError, setPopupError] = useState<string | null>(null);
  const [popupData, setPopupData] = useState<Record<string, unknown>[] | null>(null);
  const [clickedPosition, setClickedPosition] = useState<LatLngLiteral | null>(null);
  // tile layer loading state
  const [wmsPending, setWmsPending] = useState(0); // jumlah tile yang belum selesai
  const [wmsBusy, setWmsBusy] = useState(false); // grid loading

  // Reset popup content when mapset changes
  useEffect(() => {
    setClickedPosition(null);
    setPopupData(null);
    setPopupError(null);
    setPopupLoading(false);
  }, [mapset?.id]);

  useEffect(() => {
    const fetchBounds = async () => {
      if (parsed?.baseUrl && parsed?.params.layers && !parsed?.params.bounds) {
        const newBounds = await getWmsLayerBounds(parsed.baseUrl, parsed.params.layers);
        if (newBounds) {
          setBounds(newBounds);
        }
      }
    };

    fetchBounds();
  }, [parsed?.baseUrl, parsed?.params.layers, parsed?.params.bounds]);

  // safety cap: kalau ada tile nyangkut >12s, anggap selesai
  useEffect(() => {
    if (wmsPending === 0) return;
    const t = setTimeout(() => setWmsPending(0), 12000);
    return () => clearTimeout(t);
  }, [wmsPending]);

  const isMapLoading = wmsBusy;

  // Add a fallback center and zoom
  const center: [number, number] = centerCustom ?? [mapConfig.center[0], mapConfig.center[1]];
  const zoom = 8;

  return (
    <MapContainer
      key={bounds ? "with-bounds" : "without-bounds"}
      {...(bounds ? { bounds } : { center, zoom })}
      className="h-full w-full"
      style={{ height: "100%", width: "100%" }}
      attributionControl={false}
      zoomControl={isActiveControl ?? false}
      scrollWheelZoom={isActiveControl ?? false}
      dragging={isActiveControl ?? false}
      doubleClickZoom={isActiveControl ?? false}
      keyboard={isActiveControl ?? false}
      touchZoom={isActiveControl ?? false}
      boxZoom={isActiveControl ?? false}
    >
      {isShowPopup && (
        <MapClick
          parsed={parsed}
          onClick={(e) => {
            setClickedPosition(e.latlng);
            onMapClick?.(e);
          }}
          onWmsStart={() => {
            setPopupError(null);
            setPopupData(null);
            setPopupLoading(true);
          }}
          onWmsResult={({ data, error }) => {
            setPopupLoading(false);
            setPopupError(error ?? null);
            setPopupData(data ?? null);
          }}
        />
      )}
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' />
      {parsed && (
        <WMSTileLayer
          key={parsed.baseUrl + parsed.params.layers}
          url={parsed.baseUrl}
          layers={parsed.params.layers}
          styles={parsed.params.styles}
          format="image/png"
          transparent={true}
          version={parsed.params.version}
          eventHandlers={{
            loading: () => setWmsBusy(true),
            load: () => {
              setWmsBusy(false);
              setWmsPending(0);
            },
          }}
        />
      )}

      {isShowLegend && !isMapLoading && parsed && <MapLegend parsed={parsed} width={600} />}

      {isShowLoadingTileLayer && isMapLoading && (
        <div className="absolute inset-0 z-[1000] grid place-items-center bg-white/40 backdrop-blur-sm max-w-full">
          <div className="flex flex-col items-center justify-center space-y-2">
            <div className="h-10 w-10 animate-spin rounded-full border-4 border-zinc-300 border-t-zinc-900" />
            <div className="text-center font-sans font-medium">Memuat peta</div>
          </div>
        </div>
      )}
      {clickedPosition && (
        <Popup position={clickedPosition} autoPan>
          {/* loading data state */}
          {popupLoading && <div className="text-xs">Loading…</div>}
          {!popupLoading && popupError && <div className="text-xs text-red-600">{popupError}</div>}

          {/* succesfully fetched data */}
          {!popupLoading && !popupError && popupData && <PopupAttribute popupData={popupData} />}
          {!popupLoading && !popupError && popupData !== undefined && !popupData?.length && <div className="text-xs">No data available.</div>}
        </Popup>
      )}
    </MapContainer>
  );
}

// Local helper: register Leaflet click events inside MapContainer
function MapClick({
  onClick,
  onWmsStart,
  onWmsResult,
  parsed,
}: {
  onClick?: (e: LeafletMouseEvent) => void;
  onWmsStart?: () => void;
  // ⬇️ "data" untuk kirim objek JSON
  onWmsResult?: (r: { data?: Record<string, unknown>[]; html?: string; error?: string; url?: string }) => void;
  parsed: ReturnType<typeof parseWmsUrl> | null | undefined;
}) {
  useMapEvents({
    click: async (e) => {
      onClick?.(e);
      onWmsStart?.();

      try {
        if (!parsed?.baseUrl || !parsed?.params?.layers) {
          onWmsResult?.({ error: "WMS baseUrl/layers tidak tersedia" });
          return;
        }

        const map = e.target;
        const version = parsed.params.version || "1.1.1";
        const srs = parsed.params.srs || "EPSG:4326";
        const size = map.getSize();
        const bounds = map.getBounds();
        const sw = bounds.getSouthWest();
        const ne = bounds.getNorthEast();
        const pt = map.latLngToContainerPoint(e.latlng);
        const X = Math.round(pt.x);
        const Y = Math.round(pt.y);

        const urlObj = new URL(parsed.baseUrl);
        urlObj.searchParams.set("SERVICE", "WMS");
        urlObj.searchParams.set("VERSION", version);
        urlObj.searchParams.set("REQUEST", "GetFeatureInfo");
        urlObj.searchParams.set("FORMAT", "image/png");
        urlObj.searchParams.set("TRANSPARENT", "true");
        urlObj.searchParams.set("QUERY_LAYERS", parsed.params.layers);
        urlObj.searchParams.set("LAYERS", parsed.params.layers);
        urlObj.searchParams.set("STYLES", parsed.params.styles ?? "");
        urlObj.searchParams.set("exceptions", "application/vnd.ogc.se_inimage");

        // ⬇️ minta JSON dulu (kalau didukung GeoServer)
        urlObj.searchParams.set("INFO_FORMAT", "application/json");
        urlObj.searchParams.set("FEATURE_COUNT", "50");

        if (version === "1.3.0") {
          urlObj.searchParams.set("CRS", srs);
        } else {
          urlObj.searchParams.set("SRS", srs);
        }

        // BBOX lon,lat untuk 1.1.1 EPSG:4326
        urlObj.searchParams.set("BBOX", [sw.lng, sw.lat, ne.lng, ne.lat].join(","));
        urlObj.searchParams.set("WIDTH", String(size.x));
        urlObj.searchParams.set("HEIGHT", String(size.y));

        if (version === "1.3.0") {
          urlObj.searchParams.set("I", String(X));
          urlObj.searchParams.set("J", String(Y));
        } else {
          urlObj.searchParams.set("X", String(X));
          urlObj.searchParams.set("Y", String(Y));
        }

        const url = urlObj.toString();

        // --- fetch: prefer JSON, fallback HTML ---
        const res = await fetch(url, {
          headers: { Accept: "application/json, text/html;q=0.9" },
        });
        const raw = await res.text();
        if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);

        let features: Record<string, unknown>[] = [];
        const ctype = res.headers.get("content-type") || "";
        const looksJson = ctype.includes("application/json") || /^[\[\{]/.test(raw.trim());

        if (looksJson) {
          // JSON dari GeoServer (bentuk bisa variasi)
          const j = JSON.parse(raw);
          if (Array.isArray(j)) {
            features = j as Record<string, unknown>[];
          } else if (Array.isArray(j?.features)) {
            // GeoJSON-like
            features = j.features.map((f: unknown) => (typeof f === "object" && f !== null && "properties" in f ? (f as { properties: unknown }).properties : f));
          } else if (j?.properties) {
            features = [j.properties];
          } else {
            features = [j];
          }
        } else {
          // Fallback: parse HTML tabel → array objek
          const doc = new DOMParser().parseFromString(raw, "text/html");
          const tables = Array.from(doc.querySelectorAll("table"));
          for (const tbl of tables) {
            const rows = Array.from((tbl as HTMLTableElement).rows);
            if (!rows.length) continue;
            const headerRow = rows.find((r) => Array.from(r.cells).every((c) => c.tagName === "TH")) || rows[0];
            const headers = Array.from(headerRow.cells).map((c) => (c.textContent || "").trim());
            const start = rows.indexOf(headerRow) + 1;
            for (let i = start; i < rows.length; i++) {
              const r = rows[i];
              const obj: Record<string, unknown> = {};
              headers.forEach((h, idx) => {
                if (!h) return;
                obj[h] = r.cells[idx]?.textContent?.trim() ?? "";
              });
              if (Object.values(obj).some((v) => v !== "")) features.push(obj);
            }
          }
        }

        // kirim objek JSON (array of row objects)
        onWmsResult?.({ data: features, url });
      } catch (err: unknown) {
        onWmsResult?.({
          error: err instanceof Error ? err.message : "Gagal memuat GetFeatureInfo",
        });
      }
    },
  });

  return null;
}
