"use client";

import { useState, useEffect, useRef, useCallback } from "react";
import type { GeofabrikFeature, GeofabrikIndex } from "@transportationer/shared";
import { CityIngestProgress } from "@/components/city-ingest-progress";

// ─── Geofabrik region matching ────────────────────────────────────────────────

function pointInRing(point: [number, number], ring: number[][]): boolean {
  const [px, py] = point;
  let inside = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const xi = ring[i][0], yi = ring[i][1];
    const xj = ring[j][0], yj = ring[j][1];
    if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) inside = !inside;
  }
  return inside;
}

function containsPoint(geometry: GeofabrikFeature["geometry"], point: [number, number]): boolean {
  if (!geometry) return false;
  if (geometry.type === "Polygon") return pointInRing(point, geometry.coordinates[0] as number[][]);
  return (geometry.coordinates as number[][][][]).some((poly) => pointInRing(point, poly[0] as number[][]));
}

function bboxArea(geometry: GeofabrikFeature["geometry"]): number {
  if (!geometry) return Infinity;
  const coords: number[][] =
    geometry.type === "Polygon"
      ? (geometry.coordinates as number[][][]).flat()
      : (geometry.coordinates as number[][][][]).flat(2);
  const lngs = coords.map((c) => c[0]);
  const lats = coords.map((c) => c[1]);
  return (Math.max(...lngs) - Math.min(...lngs)) * (Math.max(...lats) - Math.min(...lats));
}

function findBestRegion(lat: number, lon: number, index: GeofabrikIndex): GeofabrikFeature | null {
  const point: [number, number] = [lon, lat];
  const candidates = index.features.filter(
    (f) => f.properties.urls?.pbf && f.geometry && containsPoint(f.geometry, point),
  );
  if (candidates.length === 0) return null;
  candidates.sort((a, b) => bboxArea(a.geometry) - bboxArea(b.geometry));
  return candidates[0];
}

// ─── Nominatim types + helpers ────────────────────────────────────────────────

interface NominatimResult {
  place_id: number;
  display_name: string;
  lat: string;
  lon: string;
  type: string;
  class: string;
  place_rank: number;
  geojson?: { type: string; coordinates: unknown };
  address?: {
    city?: string;
    town?: string;
    village?: string;
    municipality?: string;
    country_code?: string;
  };
}

interface SubPolygon {
  id: number;
  coordinates: number[][][];
  selected: boolean;
}

function buildActiveBoundary(polys: SubPolygon[]): { type: string; coordinates: unknown } | null {
  const active = polys.filter((p) => p.selected);
  if (active.length === 0) return null;
  if (active.length === 1) return { type: "Polygon", coordinates: active[0].coordinates };
  return { type: "MultiPolygon", coordinates: active.map((p) => p.coordinates) };
}

const COUNTRIES = [
  { code: "DE", label: "🇩🇪 Germany" },
  { code: "NL", label: "🇳🇱 Netherlands" },
  { code: "DK", label: "🇩🇰 Denmark" },
];

function toSlug(name: string): string {
  return name
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");
}

// ─── Nominatim geocoder + mini map ────────────────────────────────────────────

function LocationSelector({
  onBoundaryChange,
  onResultSelect,
}: {
  onBoundaryChange: (boundary: { type: string; coordinates: unknown } | null) => void;
  onResultSelect?: (result: { name: string; countryCode: string; lat: number; lon: number } | null) => void;
}) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<NominatimResult[]>([]);
  const [selected, setSelected] = useState<NominatimResult | null>(null);
  const [showDropdown, setShowDropdown] = useState(false);
  const [searching, setSearching] = useState(false);
  const [subPolygons, setSubPolygons] = useState<SubPolygon[]>([]);
  const subPolygonsRef = useRef<SubPolygon[]>([]);
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<import("maplibre-gl").Map | null>(null);
  const mapReadyRef = useRef(false);
  const onBoundaryChangeRef = useRef(onBoundaryChange);
  onBoundaryChangeRef.current = onBoundaryChange;
  const onResultSelectRef = useRef(onResultSelect);
  onResultSelectRef.current = onResultSelect;

  // Keep ref in sync for map click handler.
  useEffect(() => { subPolygonsRef.current = subPolygons; }, [subPolygons]);

  // When the Nominatim result changes: extract sub-polygons + notify parent + fit map.
  useEffect(() => {
    if (!selected) {
      setSubPolygons([]);
      onResultSelectRef.current?.(null);
      return;
    }

    const name =
      selected.address?.city ??
      selected.address?.town ??
      selected.address?.village ??
      selected.address?.municipality ??
      selected.display_name.split(",")[0].trim();
    onResultSelectRef.current?.({
      name,
      countryCode: (selected.address?.country_code ?? "").toUpperCase(),
      lat: parseFloat(selected.lat),
      lon: parseFloat(selected.lon),
    });

    const geojson = selected.geojson;
    if (!geojson) { setSubPolygons([]); return; }

    const polys: SubPolygon[] =
      geojson.type === "Polygon"
        ? [{ id: 0, coordinates: geojson.coordinates as number[][][], selected: true }]
        : (geojson.coordinates as number[][][][]).map((coords, i) => ({
            id: i, coordinates: coords, selected: true,
          }));
    setSubPolygons(polys);

    // Fit map to full extent of all sub-polygons.
    const map = mapRef.current;
    if (map && mapReadyRef.current) {
      const allCoords = polys.flatMap((sp) => sp.coordinates.flat()) as number[][];
      const lngs = allCoords.map((c) => c[0]);
      const lats = allCoords.map((c) => c[1]);
      map.fitBounds(
        [Math.min(...lngs), Math.min(...lats), Math.max(...lngs), Math.max(...lats)],
        { padding: 40, duration: 500 },
      );
    }
  }, [selected]);

  // Whenever sub-polygons change: update map sources + propagate active boundary.
  useEffect(() => {
    onBoundaryChangeRef.current(buildActiveBoundary(subPolygons));

    const map = mapRef.current;
    if (!map || !mapReadyRef.current) return;

    // Boundary source — one feature per sub-polygon with `selected` property for styling.
    const features = subPolygons.map((sp) => ({
      type: "Feature" as const,
      id: sp.id,
      properties: { id: sp.id, selected: sp.selected },
      geometry: { type: "Polygon" as const, coordinates: sp.coordinates },
    }));
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (map.getSource("area-boundary") as any)?.setData({ type: "FeatureCollection", features });

    // Bbox from selected sub-polygons only.
    const active = subPolygons.filter((sp) => sp.selected);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const bboxSrc = map.getSource("area-bbox") as any;
    if (active.length > 0) {
      const allCoords = active.flatMap((sp) => sp.coordinates.flat()) as number[][];
      const lngs = allCoords.map((c) => c[0]);
      const lats = allCoords.map((c) => c[1]);
      const [minLng, minLat, maxLng, maxLat] = [
        Math.min(...lngs), Math.min(...lats), Math.max(...lngs), Math.max(...lats),
      ];
      bboxSrc?.setData({
        type: "Feature",
        geometry: { type: "Polygon", coordinates: [[[minLng, minLat], [maxLng, minLat], [maxLng, maxLat], [minLng, maxLat], [minLng, minLat]]] },
        properties: {},
      });
    } else {
      bboxSrc?.setData({ type: "FeatureCollection", features: [] });
    }
  }, [subPolygons]);

  // Debounced Nominatim search.
  useEffect(() => {
    if (query.length < 2) { setResults([]); return; }
    const timer = setTimeout(async () => {
      setSearching(true);
      try {
        const res = await fetch(
          `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&polygon_geojson=1&addressdetails=1&format=json&limit=8`,
          { headers: { "User-Agent": "Transportationer/1.0 (15-minute city analyzer)" } },
        );
        const data: NominatimResult[] = await res.json();
        setResults(data.filter(
          (r) => r.geojson?.type === "Polygon" || r.geojson?.type === "MultiPolygon",
        ));
        setShowDropdown(true);
      } catch {
        setResults([]);
      } finally {
        setSearching(false);
      }
    }, 500);
    return () => clearTimeout(timer);
  }, [query]);

  // Initialize mini map.
  useEffect(() => {
    if (!mapContainerRef.current) return;
    let cancelled = false;
    (async () => {
      const mgl = await import("maplibre-gl");
      const { Protocol } = await import("pmtiles");
      if (cancelled) return;
      try { mgl.addProtocol("pmtiles", new Protocol().tile); } catch { /* already registered */ }

      const map = new mgl.Map({
        container: mapContainerRef.current!,
        style: "/tiles/style.json",
        center: [10, 51],
        zoom: 3,
      });
      mapRef.current = map;

      map.on("load", () => {
        if (cancelled) return;
        mapReadyRef.current = true;

        // Boundary — data-driven colour based on `selected` property.
        map.addSource("area-boundary", { type: "geojson", data: { type: "FeatureCollection", features: [] } });
        map.addLayer({
          id: "area-boundary-fill", type: "fill", source: "area-boundary",
          paint: {
            "fill-color": ["case", ["==", ["get", "selected"], true], "#2563eb", "#94a3b8"],
            "fill-opacity": ["case", ["==", ["get", "selected"], true], 0.15, 0.05],
          },
        });
        map.addLayer({
          id: "area-boundary-line", type: "line", source: "area-boundary",
          paint: {
            "line-color": ["case", ["==", ["get", "selected"], true], "#2563eb", "#94a3b8"],
            "line-width": ["case", ["==", ["get", "selected"], true], 2, 0.75],
          },
        });

        // Bbox rectangle.
        map.addSource("area-bbox", { type: "geojson", data: { type: "FeatureCollection", features: [] } });
        map.addLayer({
          id: "area-bbox-line", type: "line", source: "area-bbox",
          paint: { "line-color": "#f59e0b", "line-width": 1.5, "line-dasharray": [4, 3] },
        });

        // Click a sub-polygon to toggle it.
        map.on("click", "area-boundary-fill", (e) => {
          const id = e.features?.[0]?.properties?.id as number | undefined;
          if (id == null) return;
          setSubPolygons((prev) =>
            prev.map((sp) => sp.id === id ? { ...sp, selected: !sp.selected } : sp),
          );
        });
        map.on("mouseenter", "area-boundary-fill", () => { map.getCanvas().style.cursor = "pointer"; });
        map.on("mouseleave", "area-boundary-fill", () => { map.getCanvas().style.cursor = ""; });

        // Apply pending sub-polygons if the map loaded after a selection was made.
        const pending = subPolygonsRef.current;
        if (pending.length > 0) {
          const features = pending.map((sp) => ({
            type: "Feature" as const, id: sp.id,
            properties: { id: sp.id, selected: sp.selected },
            geometry: { type: "Polygon" as const, coordinates: sp.coordinates },
          }));
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          (map.getSource("area-boundary") as any)?.setData({ type: "FeatureCollection", features });
        }
      });
    })();
    return () => {
      cancelled = true;
      mapReadyRef.current = false;
      mapRef.current?.remove();
      mapRef.current = null;
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const selectedCount = subPolygons.filter((sp) => sp.selected).length;

  return (
    <div className="space-y-3">
      <div className="relative">
        <div className="flex gap-2 items-center">
          <input
            type="search"
            placeholder="Search for a city or municipality…"
            value={query}
            onChange={(e) => {
              setQuery(e.target.value);
              if (!e.target.value) { setSelected(null); setResults([]); }
            }}
            onFocus={() => results.length > 0 && setShowDropdown(true)}
            className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
          />
          {searching && <span className="text-xs text-gray-400 whitespace-nowrap">Searching…</span>}
        </div>
        {showDropdown && results.length > 0 && (
          <div className="absolute z-20 left-0 right-0 top-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-56 overflow-y-auto">
            {results.map((r) => (
              <button
                key={r.place_id}
                type="button"
                onClick={() => { setSelected(r); setQuery(r.display_name.split(",").slice(0, 3).join(", ")); setShowDropdown(false); }}
                className="w-full text-left px-3 py-2 text-sm hover:bg-gray-50 border-b border-gray-100 last:border-0"
              >
                <span className="font-medium text-gray-800">{r.display_name.split(",")[0]}</span>
                <span className="text-gray-400 ml-1 text-xs">{r.display_name.split(",").slice(1, 3).join(",")}</span>
                <span className="ml-2 text-xs text-blue-500">{r.geojson?.type}</span>
              </button>
            ))}
          </div>
        )}
      </div>

      <div ref={mapContainerRef} className="w-full rounded-md border border-gray-200 overflow-hidden" style={{ height: 220 }} />

      {subPolygons.length > 1 ? (
        <p className="text-xs text-gray-500">
          {selectedCount} of {subPolygons.length} parts selected
          {selectedCount < subPolygons.length && (
            <button
              type="button"
              onClick={() => setSubPolygons((prev) => prev.map((sp) => ({ ...sp, selected: true })))}
              className="ml-2 text-brand-600 hover:underline"
            >
              select all
            </button>
          )}
          {" · "}
          <span className="text-gray-400">click on the map to toggle</span>
        </p>
      ) : selected?.geojson ? (
        <p className="text-xs text-green-700">
          ✓ {selected.display_name.split(",").slice(0, 2).join(", ")} ({selected.geojson.type})
        </p>
      ) : (
        <p className="text-xs text-gray-400">
          Search for a city to set its administrative boundary and auto-fill the fields below.
        </p>
      )}
    </div>
  );
}

// ─── Main page ────────────────────────────────────────────────────────────────

export default function AddCityPage() {
  const [name, setName] = useState("");
  const [slug, setSlug] = useState("");
  const [countryCode, setCountryCode] = useState("");
  const [boundary, setBoundary] = useState<{ type: string; coordinates: unknown } | null>(null);
  const [matchedRegion, setMatchedRegion] = useState<GeofabrikFeature | null>(null);
  const [jobId, setJobId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const geofabrikIndexRef = useRef<GeofabrikIndex | null>(null);

  useEffect(() => {
    fetch("/api/admin/geofabrik")
      .then((r) => r.json())
      .then((data: GeofabrikIndex) => { geofabrikIndexRef.current = data; })
      .catch(() => {});
  }, []);

  const geofabrikUrl = matchedRegion?.properties.urls?.pbf ?? undefined;

  const handleBoundaryChange = useCallback(
    (b: { type: string; coordinates: unknown } | null) => setBoundary(b),
    [],
  );

  const handleResultSelect = useCallback(
    (result: { name: string; countryCode: string; lat: number; lon: number } | null) => {
      if (!result) { setMatchedRegion(null); return; }
      setName(result.name);
      setSlug(toSlug(result.name));
      setCountryCode(COUNTRIES.some((c) => c.code === result.countryCode) ? result.countryCode : "");
      setMatchedRegion(
        geofabrikIndexRef.current
          ? findBestRegion(result.lat, result.lon, geofabrikIndexRef.current)
          : null,
      );
    },
    [],
  );

  const handleSubmit = async () => {
    if (!slug || !name || !geofabrikUrl) return;
    setLoading(true);
    setError(null);
    try {
      const res = await fetch("/api/admin/cities", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          slug, name, countryCode, geofabrikUrl,
          ...(boundary ? { boundary } : {}),
        }),
      });
      if (!res.ok) {
        const data = await res.json();
        setError(data.error ?? "Failed to start ingestion");
        return;
      }
      const data = await res.json();
      setJobId(data.jobId);
    } catch (e) {
      setError(String(e));
    } finally {
      setLoading(false);
    }
  };

  if (jobId) {
    return (
      <div className="max-w-2xl mx-auto">
        <div className="flex items-center gap-4 mb-6">
          <a href="/admin" className="text-sm text-gray-500 hover:text-gray-700">← Back</a>
          <h1 className="text-2xl font-bold text-gray-900">Adding {name}…</h1>
        </div>
        <CityIngestProgress jobId={jobId} />
      </div>
    );
  }

  return (
    <div className="max-w-2xl mx-auto">
      <h1 className="text-2xl font-bold text-gray-900 mb-2">Add City</h1>
      <p className="text-sm text-gray-500 mb-6">Search for a city to configure and start ingestion.</p>

      {error && <div className="mb-4 p-3 bg-red-50 rounded text-sm text-red-700">{error}</div>}

      <div className="card space-y-5">
        <div>
          <label className="block text-sm font-medium text-gray-700 mb-2">
            City Boundary{" "}
            <span className="text-gray-400 font-normal">(optional — auto-fills fields below)</span>
          </label>
          <LocationSelector onBoundaryChange={handleBoundaryChange} onResultSelect={handleResultSelect} />
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Display Name</label>
            <input
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="Hamburg"
              className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">
              Slug <span className="text-gray-400">(URL identifier)</span>
            </label>
            <input
              value={slug}
              onChange={(e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, "-"))}
              placeholder="hamburg"
              className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm font-mono focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
            />
          </div>
        </div>

        <div className="grid grid-cols-2 gap-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">Country</label>
            <select
              value={countryCode}
              onChange={(e) => setCountryCode(e.target.value)}
              className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
            >
              <option value="">— no transit —</option>
              {COUNTRIES.map((c) => (
                <option key={c.code} value={c.code}>{c.label}</option>
              ))}
            </select>
            {!countryCode && (
              <p className="mt-1 text-xs text-amber-600">No transit scoring without a country.</p>
            )}
          </div>
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">OSM Source</label>
            <div className={`rounded-md border px-3 py-2 text-xs break-all leading-relaxed ${
              geofabrikUrl ? "border-green-200 bg-green-50 text-green-800" : "border-gray-200 bg-gray-50 text-gray-400"
            }`}>
              {geofabrikUrl
                ? geofabrikUrl.replace("https://download.geofabrik.de/", "")
                : "Select a city above"}
            </div>
          </div>
        </div>

        <div className="flex justify-end pt-1">
          <button
            onClick={handleSubmit}
            disabled={loading || !slug || !name || !geofabrikUrl}
            className="btn-primary"
          >
            {loading ? "Starting…" : "Start Ingestion"}
          </button>
        </div>
      </div>
    </div>
  );
}
