"use client";

/**
 * SaleShareMap — карта домов вторичного рынка, окрашенных по доле квартир в
 * продаже (sale_share_pct): низкая → зелёный, высокая → красный. Размер маркера
 * тоже растёт с долей. Reuses the same CDN-loaded Leaflet + OSM approach as
 * MapCard.tsx / MapPicker.tsx (no npm dep). Дома без координат пропускаем.
 *
 * Грузится через next/dynamic({ ssr:false }) из page.tsx — window.L доступен
 * только в браузере.
 */
/* eslint-disable @typescript-eslint/no-explicit-any -- интероп с CDN-библиотекой Leaflet */
import { useEffect, useMemo, useRef, useState } from "react";

import type { BuildingSaleShare } from "@/types/sale-share";
import { fmtPct, heatColor, markerRadius } from "./saleShareUtils";

const LEAFLET_VER = "1.9.4";
const LEAFLET_CSS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.css`;
const LEAFLET_JS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.js`;
const LEAFLET_CSS_SRI = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=";
const LEAFLET_JS_SRI = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=";
const EKB_CENTER: [number, number] = [56.8389, 60.6057];

/** Подгружает Leaflet с CDN один раз, резолвит window.L. (mirror MapCard) */
function loadLeaflet(): Promise<any> {
  return new Promise((resolve, reject) => {
    const w = window as any;
    if (w.L) {
      resolve(w.L);
      return;
    }
    if (!document.querySelector(`link[data-leaflet]`)) {
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.href = LEAFLET_CSS;
      link.integrity = LEAFLET_CSS_SRI;
      link.crossOrigin = "anonymous";
      link.setAttribute("data-leaflet", "1");
      document.head.appendChild(link);
    }
    const existing = document.querySelector<HTMLScriptElement>(`script[data-leaflet]`);
    if (existing) {
      existing.addEventListener("load", () => resolve(w.L));
      existing.addEventListener("error", () => reject(new Error("leaflet load failed")));
      return;
    }
    const script = document.createElement("script");
    script.src = LEAFLET_JS;
    script.integrity = LEAFLET_JS_SRI;
    script.crossOrigin = "anonymous";
    script.setAttribute("data-leaflet", "1");
    script.onload = () => resolve(w.L);
    script.onerror = () => reject(new Error("leaflet load failed"));
    document.body.appendChild(script);
  });
}

/** Безопасное экранирование для вставки в HTML popup. */
function esc(s: string): string {
  return s.replace(/[&<>"']/g, (c) =>
    c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : c === '"' ? "&quot;" : "&#39;",
  );
}

function popupHtml(b: BuildingSaleShare): string {
  const badges: string[] = [];
  if (b.is_emergency) {
    badges.push(
      `<span style="background:#fef2f2;color:#b91c1c;border-radius:4px;padding:1px 6px;font-size:11px">аварийный</span>`,
    );
  }
  if (b.over_100) {
    badges.push(
      `<span style="background:#fef9c3;color:#854d0e;border-radius:4px;padding:1px 6px;font-size:11px">возможно, несколько корпусов</span>`,
    );
  }
  const denom =
    b.active_secondary !== null && b.flat_count_effective !== null
      ? `${b.active_secondary} из ${b.flat_count_effective} квартир`
      : b.active_secondary !== null
        ? `${b.active_secondary} объявлений`
        : "";
  return `<div style="font-size:12px;line-height:1.5;min-width:180px">
    <b>${esc(b.address ?? "Адрес не указан")}</b><br/>
    <span style="color:${heatColor(b.sale_share_pct)};font-weight:600">${esc(fmtPct(b.sale_share_pct))}</span> в продаже${denom ? ` · ${esc(denom)}` : ""}
    ${badges.length ? `<br/><span style="display:inline-flex;gap:4px;flex-wrap:wrap;margin-top:4px">${badges.join(" ")}</span>` : ""}
  </div>`;
}

interface Props {
  buildings: BuildingSaleShare[];
  selectedHouseId: number | null;
  hoveredHouseId: number | null;
  onSelect: (houseId: number) => void;
  onHover: (houseId: number | null) => void;
}

export function SaleShareMap({
  buildings,
  selectedHouseId,
  hoveredHouseId,
  onSelect,
  onHover,
}: Props) {
  const mapRef = useRef<HTMLDivElement>(null);
  const mapObj = useRef<any>(null);
  const LRef = useRef<any>(null);
  const layerRef = useRef<any>(null);
  const markers = useRef<Map<number, any>>(new Map());
  const [ready, setReady] = useState(false);
  const [mapError, setMapError] = useState(false);

  const geoBuildings = useMemo(
    () =>
      buildings.filter(
        (b) => typeof b.lat === "number" && typeof b.lon === "number",
      ),
    [buildings],
  );

  // Init map once.
  useEffect(() => {
    let cancelled = false;
    loadLeaflet()
      .then((L) => {
        if (cancelled || !mapRef.current) return;
        LRef.current = L;
        const map = L.map(mapRef.current).setView(EKB_CENTER, 11);
        L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
          attribution: "© OpenStreetMap",
          maxZoom: 19,
        }).addTo(map);
        layerRef.current = L.layerGroup().addTo(map);
        mapObj.current = map;
        setTimeout(() => map && map.invalidateSize(), 120);
        setReady(true);
      })
      .catch(() => setMapError(true));
    return () => {
      cancelled = true;
      if (mapObj.current) {
        mapObj.current.remove();
        mapObj.current = null;
      }
    };
  }, []);

  // (Re)draw markers when the building set changes.
  useEffect(() => {
    if (!ready) return;
    const L = LRef.current;
    const layer = layerRef.current;
    const map = mapObj.current;
    if (!L || !layer || !map) return;
    layer.clearLayers();
    markers.current.clear();
    const bounds: [number, number][] = [];
    for (const b of geoBuildings) {
      const ll: [number, number] = [b.lat as number, b.lon as number];
      bounds.push(ll);
      const marker = L.circleMarker(ll, {
        radius: markerRadius(b.sale_share_pct),
        color: "#ffffff",
        weight: 1.5,
        fillColor: heatColor(b.sale_share_pct),
        fillOpacity: 0.85,
      });
      marker.bindPopup(popupHtml(b));
      marker.on("click", () => onSelect(b.house_id));
      marker.on("mouseover", () => onHover(b.house_id));
      marker.on("mouseout", () => onHover(null));
      marker.addTo(layer);
      markers.current.set(b.house_id, marker);
    }
    if (bounds.length > 1) {
      map.fitBounds(bounds, { padding: [30, 30], maxZoom: 15 });
    } else if (bounds.length === 1) {
      map.setView(bounds[0], 14);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- onSelect/onHover стабильны (useCallback в page)
  }, [ready, geoBuildings]);

  // Focus the selected building: pan + open popup.
  useEffect(() => {
    if (!ready || selectedHouseId === null) return;
    const marker = markers.current.get(selectedHouseId);
    const map = mapObj.current;
    if (marker && map) {
      map.panTo(marker.getLatLng(), { animate: true, duration: 0.3 });
      marker.openPopup();
    }
  }, [ready, selectedHouseId]);

  // Highlight the hovered building's marker (sync with list row hover).
  useEffect(() => {
    if (!ready) return;
    markers.current.forEach((marker, id) => {
      const b = geoBuildings.find((x) => x.house_id === id);
      if (!b) return;
      const isHover = id === hoveredHouseId;
      marker.setStyle({ weight: isHover ? 3 : 1.5, fillOpacity: isHover ? 1 : 0.85 });
      marker.setRadius(
        isHover ? markerRadius(b.sale_share_pct) + 3 : markerRadius(b.sale_share_pct),
      );
    });
  }, [ready, hoveredHouseId, geoBuildings]);

  if (mapError) {
    return (
      <div className="ss-map-wrap">
        <div className="ss-map-empty" style={{ color: "var(--danger, #b3261e)" }}>
          Не удалось загрузить карту. Проверьте интернет-соединение.
        </div>
      </div>
    );
  }

  return (
    <div className="ss-map-wrap">
      <div
        ref={mapRef}
        className="ss-map"
        aria-label="Карта домов по доле квартир в продаже"
      />
      {ready && geoBuildings.length === 0 && (
        <div className="ss-map-empty">
          Нет домов с координатами для отображения на карте.
        </div>
      )}
    </div>
  );
}
