"use client";

/* Выбор адреса на карте ЕКБ (Leaflet + OSM). Клик по дому → reverse-геокодинг. */
/* eslint-disable @typescript-eslint/no-explicit-any -- интероп с CDN-библиотекой Leaflet */
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

import { API_BASE_URL } from "@/lib/api";

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];

/** Precision значения от reverse endpoint, при которых имеет смысл двигать
 * marker на snapped point (центр здания от Yandex / нашего FDW). Для
 * street/range/near/locality marker остаётся на клике — иначе соврём
 * пользователю, что нашли точное здание. */
const SNAP_PRECISIONS = new Set(["exact", "number", "cadastral"]);

/** Минимальная дистанция click→snap в метрах для перемещения marker'а:
 * меньше — не двигаем (иначе marker «дёргается» при кликах прямо в дом). */
const SNAP_MIN_DISTANCE_M = 5;

/** Расстояние в метрах между двумя точками (haversine, достаточно точно для <1km). */
function haversineMeters(
  lat1: number,
  lon1: number,
  lat2: number,
  lon2: number,
): number {
  const R = 6_371_000; // м
  const toRad = (d: number) => (d * Math.PI) / 180;
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(a));
}

interface ReverseResponse {
  address: string;
  lat: number;
  lon: number;
  snapped_lat: number;
  snapped_lon: number;
  precision: string;
  provider: string;
}

/** Подгружает Leaflet с CDN один раз, резолвит window.L. */
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);
  });
}

interface Props {
  onPick: (address: string) => void;
  onClose: () => void;
}

export function MapPicker({ onPick, onClose }: Props) {
  const mapRef = useRef<HTMLDivElement>(null);
  const [address, setAddress] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [mapError, setMapError] = useState(false);
  // True если последний reverse snap'нул marker на точку здания. Показываем
  // тонкий hint «Точка дома по Яндексу», чтобы user понимал почему marker
  // переехал на пару метров от его клика.
  const [snapped, setSnapped] = useState(false);
  // Portal-mount guard (SSR-safe): document доступен только после монтирования.
  // Рендерим оверлей в document.body, чтобы position:fixed/z-index:1000 не были
  // заперты в stacking-context формы (#771: полоски SourcesProgress наезжали).
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);

  useEffect(() => {
    let map: any = null;
    let marker: any = null;
    let cancelled = false;

    loadLeaflet()
      .then((L) => {
        if (cancelled || !mapRef.current) return;
        map = L.map(mapRef.current).setView(EKB_CENTER, 12);
        L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
          attribution: "© OpenStreetMap",
          maxZoom: 19,
        }).addTo(map);
        setTimeout(() => map && map.invalidateSize(), 120);

        map.on("click", (e: any) => {
          const lat = e.latlng.lat as number;
          const lon = e.latlng.lng as number;
          if (marker) marker.setLatLng([lat, lon]);
          else
            marker = L.circleMarker([lat, lon], {
              radius: 8,
              color: "#1d4ed8",
              weight: 2,
              fillColor: "#1d4ed8",
              fillOpacity: 0.45,
            }).addTo(map);
          setAddress(null);
          setSnapped(false);
          setLoading(true);
          fetch(`${API_BASE_URL}/api/v1/geocode/reverse?lat=${lat}&lon=${lon}`)
            .then((r) => (r.ok ? (r.json() as Promise<ReverseResponse>) : null))
            .then((d) => {
              if (cancelled || !d) {
                setAddress(null);
                return;
              }
              setAddress(d.address);
              // Snap marker → центр здания если provider дал точное precision
              // и snapped >5m от клика (иначе скачок незаметен / создаёт jitter).
              if (
                SNAP_PRECISIONS.has(d.precision) &&
                typeof d.snapped_lat === "number" &&
                typeof d.snapped_lon === "number"
              ) {
                const dist = haversineMeters(
                  lat,
                  lon,
                  d.snapped_lat,
                  d.snapped_lon,
                );
                if (dist >= SNAP_MIN_DISTANCE_M && marker && map) {
                  marker.setLatLng([d.snapped_lat, d.snapped_lon]);
                  map.panTo([d.snapped_lat, d.snapped_lon], {
                    animate: true,
                    duration: 0.3,
                  });
                  setSnapped(true);
                }
              }
            })
            .catch(() => setAddress(null))
            .finally(() => {
              if (!cancelled) setLoading(false);
            });
        });
      })
      .catch(() => setMapError(true));

    return () => {
      cancelled = true;
      if (map) map.remove();
    };
  }, []);

  // Focus management: initial focus to close button, restore on unmount, Esc closes
  const closeBtnRef = useRef<HTMLButtonElement>(null);
  const previouslyFocused = useRef<HTMLElement | null>(null);

  useEffect(() => {
    previouslyFocused.current = document.activeElement as HTMLElement | null;
    closeBtnRef.current?.focus();
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") {
        onClose();
      }
    };
    window.addEventListener("keydown", onKey);
    return () => {
      window.removeEventListener("keydown", onKey);
      previouslyFocused.current?.focus();
    };
  }, [onClose]);

  // Focus trap: keep Tab/Shift+Tab inside the modal
  const dialogRef = useRef<HTMLDivElement>(null);
  const onDialogKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
    if (e.key !== "Tab" || !dialogRef.current) return;
    const focusables = dialogRef.current.querySelectorAll<HTMLElement>(
      'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
    );
    if (focusables.length === 0) return;
    const first = focusables[0];
    const last = focusables[focusables.length - 1];
    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  };

  if (!mounted) return null;

  return createPortal(
    <div
      onClick={onClose}
      style={{
        position: "fixed",
        inset: 0,
        background: "rgba(0,0,0,.5)",
        zIndex: 1000,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: 20,
      }}
    >
      <div
        ref={dialogRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="map-picker-title"
        onClick={(e) => e.stopPropagation()}
        onKeyDown={onDialogKeyDown}
        style={{
          background: "#fff",
          borderRadius: 12,
          width: "min(900px, 100%)",
          maxHeight: "92vh",
          display: "flex",
          flexDirection: "column",
          overflow: "hidden",
        }}
      >
        <div
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            padding: "12px 16px",
            borderBottom: "1px solid #e5e5e5",
          }}
        >
          <b id="map-picker-title">Выбор адреса на карте ЕКБ</b>
          <button
            ref={closeBtnRef}
            type="button"
            onClick={onClose}
            aria-label="Закрыть"
            style={{ border: "none", background: "none", fontSize: 22, cursor: "pointer", lineHeight: 1 }}
          >
            ×
          </button>
        </div>

        {mapError ? (
          <div style={{ padding: 48, textAlign: "center", color: "#c0392b" }}>
            Не удалось загрузить карту. Проверьте интернет-соединение.
          </div>
        ) : (
          <div ref={mapRef} style={{ height: 460, width: "100%", background: "#eef1f4" }} />
        )}

        <div
          style={{
            padding: "12px 16px",
            borderTop: "1px solid #e5e5e5",
            display: "flex",
            gap: 12,
            alignItems: "center",
          }}
        >
          <div style={{ flex: 1, fontSize: 14, color: address ? "#111" : "#888" }}>
            {loading
              ? "Определяем адрес…"
              : address
                ? address
                : "Кликните по дому на карте"}
            {address && snapped && !loading ? (
              <div
                style={{
                  fontSize: 12,
                  color: "var(--fg-tertiary)",
                  marginTop: 2,
                }}
                aria-live="polite"
              >
                Точка дома по Яндексу
              </div>
            ) : null}
          </div>
          <button
            type="button"
            onClick={() => {
              if (address) {
                onPick(address);
                onClose();
              }
            }}
            disabled={!address || loading}
            className="btn btn-primary"
            style={{ opacity: address && !loading ? 1 : 0.5, whiteSpace: "nowrap" }}
          >
            Подставить адрес
          </button>
        </div>
      </div>
    </div>,
    document.body,
  );
}
