"use client";

import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { X, MapPin, Crosshair, Loader2, Check } from "lucide-react";

/**
 * Pick an address by dropping a pin. Uses OpenStreetMap tiles directly —
 * no library and no API key, which keeps the project dependency-free.
 */
/** Full official names are unwieldy on a card. */
const SHORT_COUNTRY: Record<string, string> = {
  GB: "United Kingdom",
  US: "United States",
  AE: "United Arab Emirates",
  KR: "South Korea",
  RU: "Russia",
  CZ: "Czechia",
};

export function AddressMap({
  onClose,
  onPick,
}: {
  onClose: () => void;
  onPick: (label: string, at?: { lat: number; lon: number }) => void;
}) {
  // Southend-on-Sea as a sensible default before we know better.
  const [centre, setCentre] = useState({ lat: 51.5459, lng: 0.7077 });
  const [zoom, setZoom] = useState(14);
  const [label, setLabel] = useState("");
  const [looking, setLooking] = useState(false);
  const dragging = useRef<{ x: number; y: number } | null>(null);

  /** Slippy-map tile maths: which tile covers this coordinate. */
  const tile = (lat: number, lng: number, z: number) => {
    const n = 2 ** z;
    const x = ((lng + 180) / 360) * n;
    const latRad = (lat * Math.PI) / 180;
    const y =
      ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n;
    return { x, y };
  };

  async function lookupAddress(lat: number, lng: number) {
    setLooking(true);

    // Nominatim is the OpenStreetMap geocoder — same data as the tiles, and
    // it returns a real street name rather than a continent.
    const r = await fetch(
      `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`,
      { headers: { Accept: "application/json" } }
    ).catch(() => null);

    if (r && r.ok) {
      const d = await r.json();
      const a = d.address ?? {};

      const street = a.road || a.pedestrian || a.footway || a.neighbourhood;
      const city = a.city || a.town || a.village || a.suburb || a.county;
      const country = SHORT_COUNTRY[(a.country_code ?? "").toUpperCase()] ?? a.country;

      const parts = [street, city, country].filter(Boolean);
      setLabel(parts.filter((p, i) => parts.indexOf(p) === i).join(", "));
    }

    setLooking(false);
  }

  useEffect(() => {
    lookupAddress(centre.lat, centre.lng);
    // Only when the pin settles, not on every pixel of a drag.
  }, [centre.lat, centre.lng]);

  const t = tile(centre.lat, centre.lng, zoom);
  const size = 256;
  const cols = 3;
  const rows = 3;

  return createPortal(
    <div className="nm-back" onClick={onClose}>
      <div className="am-card" onClick={(e) => e.stopPropagation()}>
        <div className="cl-head">
          <span className="cl-title">Pick your address</span>
          <button onClick={onClose} className="nm-x">
            <X size={18} />
          </button>
        </div>

        <div
          className="am-map"
          onPointerDown={(e) => {
            dragging.current = { x: e.clientX, y: e.clientY };
            (e.target as HTMLElement).setPointerCapture(e.pointerId);
          }}
          onPointerMove={(e) => {
            if (!dragging.current) return;
            const dx = e.clientX - dragging.current.x;
            const dy = e.clientY - dragging.current.y;
            dragging.current = { x: e.clientX, y: e.clientY };
            // Convert pixels dragged into a shift in latitude/longitude.
            const scale = 360 / (256 * 2 ** zoom);
            setCentre((c) => ({
              lat: Math.max(-85, Math.min(85, c.lat + dy * scale * 0.75)),
              lng: c.lng - dx * scale,
            }));
          }}
          onPointerUp={() => (dragging.current = null)}
        >
          {Array.from({ length: rows }).map((_, ry) =>
            Array.from({ length: cols }).map((_, rx) => {
              const tx = Math.floor(t.x) + rx - 1;
              const ty = Math.floor(t.y) + ry - 1;
              return (
                // eslint-disable-next-line @next/next/no-img-element
                <img
                  key={`${rx}-${ry}`}
                  src={`https://tile.openstreetmap.org/${zoom}/${tx}/${ty}.png`}
                  alt=""
                  draggable={false}
                  style={{
                    position: "absolute",
                    width: size,
                    height: size,
                    left: `calc(50% + ${(tx - t.x) * size}px)`,
                    top: `calc(50% + ${(ty - t.y) * size}px)`,
                  }}
                />
              );
            })
          )}

          <span className="am-pin">
            <MapPin size={30} />
          </span>

          <div className="am-zoom">
            <button onClick={() => setZoom((z) => Math.min(18, z + 1))}>+</button>
            <button onClick={() => setZoom((z) => Math.max(3, z - 1))}>−</button>
          </div>

          <button
            className="am-locate"
            onClick={() =>
              navigator.geolocation?.getCurrentPosition((pos) =>
                setCentre({ lat: pos.coords.latitude, lng: pos.coords.longitude })
              )
            }
            title="Use my location"
          >
            <Crosshair size={15} />
          </button>
        </div>

        <div className="am-foot">
          <p className="am-label">
            {looking ? (
              <>
                <Loader2 size={13} className="animate-spin" /> Finding address…
              </>
            ) : (
              label || "Drag the map to place the pin"
            )}
          </p>
          <button
            // The coordinates go with the label, so distance filters have
            // something to work from.
            onClick={() => onPick(label, { lat: centre.lat, lon: centre.lng })}
            disabled={!label || looking}
            className="am-save"
          >
            <Check size={15} /> Use this address
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}
