"use client";

import "leaflet/dist/leaflet.css";
import * as React from "react";
import { Loader2, MapPin as MapPinIcon, Search, X } from "lucide-react";
import L from "leaflet";
import {
  Circle,
  MapContainer,
  Marker,
  TileLayer,
  ZoomControl,
  useMap,
  useMapEvents,
} from "react-leaflet";

import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
} from "@/components/ui/input-group";
import { Button } from "@/components/ui/button";
import { Typography } from "@/components/ui/typography";
import { useTranslation } from "@/hooks/useTranslation";
import { geocodePlace, type GeocodeResult } from "@/lib/api/geocode.api";

export interface LatLng {
  lat: number;
  lng: number;
}

/** Pilot city — the map opens here until the owner drops a pin. */
const JEDDAH: [number, number] = [21.5433, 39.1728];
/** Minimum characters before a type-ahead search fires. */
const MIN_QUERY = 3;

type SearchStatus = "idle" | "loading" | "empty" | "error";

/**
 * A divIcon pin styled with design tokens: the wrapper carries `text-primary-blue`
 * and the SVG fills with `currentColor`, so no hardcoded hex and no reliance on
 * Leaflet's default marker images (which break under bundlers). An empty-ish
 * className avoids Leaflet's default white `leaflet-div-icon` box.
 */
const pinIcon = L.divIcon({
  className: "washy-map-pin",
  html: `<span class="block text-primary-blue drop-shadow-sm">
    <svg xmlns="http://www.w3.org/2000/svg" width="32" height="40" viewBox="0 0 24 24" fill="currentColor" stroke="white" stroke-width="1.5">
      <path d="M12 21.7C17.3 17 20 13 20 10a8 8 0 1 0-16 0c0 3 2.7 7 8 11.7z"/>
      <circle cx="12" cy="10" r="3" fill="white" stroke="none"/>
    </svg>
  </span>`,
  iconSize: [32, 40],
  iconAnchor: [16, 40],
});

function ClickToPlace({ onPick }: { onPick: (coords: LatLng) => void }) {
  useMapEvents({
    click(event) {
      onPick({ lat: event.latlng.lat, lng: event.latlng.lng });
    },
  });
  return null;
}

/**
 * Recenters the map when `target` changes identity (set on search select, not on
 * plain map clicks — a clicked point is already in view). Calls an imperative
 * Leaflet API in an effect, so no cascading state updates.
 */
function MapController({ target }: { target: LatLng | null }) {
  const map = useMap();
  React.useEffect(() => {
    if (target) {
      map.flyTo([target.lat, target.lng], Math.max(map.getZoom(), 14), {
        duration: 0.7,
      });
    }
  }, [target, map]);
  return null;
}

interface StationMapPickerProps {
  /** Current pin, or null when the owner hasn't placed one yet */
  value: LatLng | null;
  onChange: (coords: LatLng) => void;
  /** Coverage radius in km — drawn as a live circle around the pin */
  radiusKm: number;
  /**
   * Returns the address the owner typed in the fields above, joined into a
   * geocoding query. Read lazily (on button click) so typing there doesn't
   * re-render the map. Setting the pin from it never edits those fields.
   */
  getAddressQuery: () => string;
}

/**
 * Click-or-drag map picker for the service-station coordinates, with an on-map
 * search bar (type-ahead) and a "use the address above" shortcut — both via free
 * OSM Nominatim. Leaflet + OSM, no API key. This is the ONLY module importing
 * Leaflet on this screen — its parent loads it via `next/dynamic` with
 * `ssr: false` because Leaflet reads `window` at import time. Search only moves
 * the pin (lat/lng); the address text inputs above are never changed, and the
 * pin stays draggable. The coverage circle updates live with `radiusKm`.
 */
export function StationMapPicker({
  value,
  onChange,
  radiusKm,
  getAddressQuery,
}: StationMapPickerProps) {
  const { t, language } = useTranslation();
  const center: [number, number] = value ? [value.lat, value.lng] : JEDDAH;

  const [focus, setFocus] = React.useState<LatLng | null>(null);
  const [query, setQuery] = React.useState("");
  const [results, setResults] = React.useState<GeocodeResult[]>([]);
  const [status, setStatus] = React.useState<SearchStatus>("idle");
  const [addressEmpty, setAddressEmpty] = React.useState(false);

  // Debounced type-ahead search. All state writes happen in the async timeout /
  // cleanup, never synchronously in the effect body (keeps the compiler happy),
  // and the in-flight request is aborted when the query changes.
  React.useEffect(() => {
    const q = query.trim();
    if (q.length < MIN_QUERY) return;
    const controller = new AbortController();
    const timer = setTimeout(() => {
      geocodePlace(q, { language, signal: controller.signal })
        .then((found) => {
          setResults(found);
          setStatus(found.length ? "idle" : "empty");
        })
        .catch(() => {
          if (!controller.signal.aborted) setStatus("error");
        });
    }, 450);
    return () => {
      clearTimeout(timer);
      controller.abort();
    };
  }, [query, language]);

  function onQueryChange(next: string) {
    setQuery(next);
    setAddressEmpty(false);
    if (next.trim().length < MIN_QUERY) {
      setResults([]);
      setStatus("idle");
    } else {
      // Keep any previous results visible under the spinner until fresh ones land.
      setStatus("loading");
    }
  }

  function clearSearch() {
    setQuery("");
    setResults([]);
    setStatus("idle");
  }

  function select(result: GeocodeResult) {
    const coords = { lat: result.lat, lng: result.lng };
    onChange(coords);
    setFocus({ ...coords }); // fresh identity → MapController flies here
    setResults([]);
    setStatus("idle");
  }

  function handleUseAddress() {
    const q = getAddressQuery();
    if (!q.trim()) {
      setAddressEmpty(true);
      return;
    }
    onQueryChange(q); // prefill the search → type-ahead shows matches to pick
  }

  const trimmed = query.trim();
  const showDropdown =
    trimmed.length >= MIN_QUERY &&
    (results.length > 0 ||
      status === "loading" ||
      status === "empty" ||
      status === "error");

  return (
    <div className="flex flex-col gap-2">
      <div className="relative">
        <MapContainer
          center={center}
          zoom={value ? 12 : 11}
          scrollWheelZoom={false}
          zoomControl={false}
          className="h-[300px] w-full rounded-[12px] sm:h-[360px]"
        >
          <TileLayer
            url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
            attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            maxZoom={19}
          />
          <ZoomControl position="bottomright" />
          <ClickToPlace onPick={onChange} />
          <MapController target={focus} />
          {value && (
            <>
              <Circle
                center={[value.lat, value.lng]}
                radius={Math.max(radiusKm, 0) * 1000}
                pathOptions={{
                  className: "fill-primary-blue/10 stroke-primary-blue",
                  weight: 2,
                }}
              />
              <Marker
                position={[value.lat, value.lng]}
                icon={pinIcon}
                draggable
                eventHandlers={{
                  dragend(event) {
                    const marker = event.target as L.Marker;
                    const { lat, lng } = marker.getLatLng();
                    onChange({ lat, lng });
                  },
                }}
              />
            </>
          )}
        </MapContainer>

        {/* On-map search overlay (sibling of the map, so it never triggers a
            map click / pan). z-[1000] sits above Leaflet's panes and controls. */}
        <div className="absolute inset-x-3 top-3 z-1000 flex flex-col gap-1.5">
          <InputGroup className="bg-white shadow-md">
            <InputGroupAddon align="inline-start">
              <Search className="size-4 text-secondary-text" />
            </InputGroupAddon>
            <InputGroupInput
              value={query}
              onChange={(event) => onQueryChange(event.target.value)}
              onKeyDown={(event) => {
                // Don't let Enter submit the registration form.
                if (event.key === "Enter") event.preventDefault();
              }}
              placeholder={t(
                "auth.registerProvider.location.searchPlaceholder",
              )}
              aria-label={t("auth.registerProvider.location.search")}
            />
            {status === "loading" ? (
              <InputGroupAddon align="inline-end">
                <Loader2 className="size-4 animate-spin text-primary-blue" />
              </InputGroupAddon>
            ) : query ? (
              <InputGroupAddon align="inline-end">
                <InputGroupButton
                  type="button"
                  onClick={clearSearch}
                  aria-label={t("auth.registerProvider.location.clearSearch")}
                >
                  <X className="size-4 text-secondary-text" />
                </InputGroupButton>
              </InputGroupAddon>
            ) : null}
          </InputGroup>

          {showDropdown && (
            <div className="overflow-hidden rounded-[12px] border border-input bg-white shadow-lg">
              {results.length > 0 ? (
                <ul className="flex max-h-[176px] flex-col gap-0.5 overflow-y-auto p-1">
                  {results.map((result, index) => (
                    <li key={`${result.lat}-${result.lng}-${index}`}>
                      <button
                        type="button"
                        onClick={() => select(result)}
                        className="flex w-full items-start gap-2 rounded-[8px] p-2 text-start transition-colors hover:bg-muted"
                      >
                        <MapPinIcon className="mt-0.5 size-4 shrink-0 text-primary-blue" />
                        <Typography
                          as="span"
                          variant="caption"
                          className="line-clamp-2"
                        >
                          {result.label}
                        </Typography>
                      </button>
                    </li>
                  ))}
                </ul>
              ) : status === "loading" ? (
                <div className="flex items-center gap-2 p-3">
                  <Loader2 className="size-4 shrink-0 animate-spin text-primary-blue" />
                  <Typography as="span" variant="caption" color="secondary">
                    {t("auth.registerProvider.location.searching")}
                  </Typography>
                </div>
              ) : status === "error" ? (
                <Typography
                  as="p"
                  variant="caption"
                  className="text-destructive p-3"
                >
                  {t("auth.registerProvider.location.searchError")}
                </Typography>
              ) : (
                <Typography
                  as="p"
                  variant="caption"
                  color="secondary"
                  className="p-3"
                >
                  {t("auth.registerProvider.location.noResults")}
                </Typography>
              )}
            </div>
          )}
        </div>
      </div>

      {/* Prefill the search from the address the owner already typed above. */}
      <div className="flex flex-col gap-1">
        <Button
          type="button"
          variant="ghost"
          size="sm"
          className="self-start gap-1.5"
          onClick={handleUseAddress}
        >
          <MapPinIcon className="size-3.5" />
          {t("auth.registerProvider.location.useEnteredAddress")}
        </Button>
        {addressEmpty && (
          <Typography as="p" variant="caption" className="text-destructive">
            {t("auth.registerProvider.location.enterAddressFirst")}
          </Typography>
        )}
      </div>
    </div>
  );
}
