"use client";

import { useState, useEffect, useMemo, useRef, useCallback } from "react";
import type { GeofabrikFeature, GeofabrikIndex } from "@transportationer/shared";
import { useJobProgress } from "@/hooks/use-job-progress";

type Step = "browse" | "confirm" | "ingest";

// ─── Step indicator ───────────────────────────────────────────────────────────

function StepIndicator({ current }: { current: Step }) {
  const steps: { key: Step; label: string }[] = [
    { key: "browse", label: "Select Region" },
    { key: "confirm", label: "Confirm" },
    { key: "ingest", label: "Processing" },
  ];
  const idx = steps.findIndex((s) => s.key === current);
  return (
    <nav className="flex items-center gap-4 mb-8">
      {steps.map((s, i) => (
        <div key={s.key} className="flex items-center gap-2">
          <span
            className={`w-6 h-6 rounded-full text-xs flex items-center justify-center font-medium ${
              i < idx
                ? "bg-brand-600 text-white"
                : i === idx
                  ? "bg-brand-600 text-white ring-2 ring-brand-300"
                  : "bg-gray-200 text-gray-500"
            }`}
          >
            {i < idx ? "✓" : i + 1}
          </span>
          <span
            className={`text-sm font-medium ${i === idx ? "text-gray-900" : "text-gray-500"}`}
          >
            {s.label}
          </span>
          {i < steps.length - 1 && (
            <span className="text-gray-300 mx-2">→</span>
          )}
        </div>
      ))}
    </nav>
  );
}

// ─── Geofabrik browser ────────────────────────────────────────────────────────

function GeofabrikBrowser({
  onSelect,
}: {
  onSelect: (f: GeofabrikFeature) => void;
}) {
  const [index, setIndex] = useState<GeofabrikIndex | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [query, setQuery] = useState("");
  const [parent, setParent] = useState<string | undefined>(undefined);

  useEffect(() => {
    fetch("/api/admin/geofabrik")
      .then((r) => r.json())
      .then(setIndex)
      .catch((e) => setError(String(e)))
      .finally(() => setLoading(false));
  }, []);

  const features = useMemo(() => {
    if (!index) return [];
    return index.features.filter((f) => {
      const matchesParent = parent
        ? f.properties.parent === parent
        : !f.properties.parent;
      const matchesQuery = query
        ? f.properties.name.toLowerCase().includes(query.toLowerCase()) ||
          f.properties.id.toLowerCase().includes(query.toLowerCase())
        : true;
      return matchesParent && matchesQuery && f.properties.urls?.pbf;
    });
  }, [index, parent, query]);

  const parentFeature = index?.features.find(
    (f) => f.properties.id === parent,
  );
  const grandParent = parentFeature
    ? index?.features.find(
        (f) => f.properties.id === parentFeature.properties.parent,
      )
    : null;

  if (loading)
    return (
      <div className="card py-12 text-center text-gray-500">
        Loading Geofabrik region index…
      </div>
    );
  if (error)
    return (
      <div className="card py-8 text-center text-red-600">
        Error loading index: {error}
      </div>
    );

  return (
    <div className="card">
      <h2 className="text-lg font-semibold mb-4">Select a Region</h2>

      <div className="flex items-center gap-2 text-sm mb-4 flex-wrap">
        <button
          onClick={() => setParent(undefined)}
          className="text-brand-600 hover:underline"
        >
          All regions
        </button>
        {grandParent && (
          <>
            <span className="text-gray-400">›</span>
            <button
              onClick={() => setParent(grandParent.properties.id)}
              className="text-brand-600 hover:underline"
            >
              {grandParent.properties.name}
            </button>
          </>
        )}
        {parentFeature && (
          <>
            <span className="text-gray-400">›</span>
            <span className="font-medium">{parentFeature.properties.name}</span>
          </>
        )}
      </div>

      <input
        type="search"
        placeholder="Search regions…"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm mb-4 focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
      />

      <div className="divide-y divide-gray-100 max-h-80 overflow-y-auto rounded border border-gray-200">
        {features.length === 0 ? (
          <p className="text-sm text-gray-500 py-4 px-3">No regions found.</p>
        ) : (
          features.map((f) => {
            const hasChildren = index!.features.some(
              (c) => c.properties.parent === f.properties.id,
            );
            return (
              <div
                key={f.properties.id}
                className="flex items-center justify-between px-3 py-2.5 hover:bg-gray-50"
              >
                <div>
                  <p className="text-sm font-medium text-gray-900">
                    {f.properties.name}
                  </p>
                  <p className="text-xs text-gray-400">{f.properties.id}</p>
                </div>
                <div className="flex gap-2">
                  {hasChildren && (
                    <button
                      onClick={() => {
                        setParent(f.properties.id);
                        setQuery("");
                      }}
                      className="btn-secondary text-xs py-1"
                    >
                      Browse →
                    </button>
                  )}
                  <button
                    onClick={() => onSelect(f)}
                    className="btn-primary text-xs py-1"
                  >
                    Select
                  </button>
                </div>
              </div>
            );
          })
        )}
      </div>
    </div>
  );
}

// ─── Nominatim geocoder + radius selector + mini map ─────────────────────────

interface NominatimResult {
  place_id: number;
  display_name: string;
  lat: string;
  lon: string;
  type: string;
  class: string;
}

const RADIUS_OPTIONS = [2, 5, 10, 15, 20, 30];

function computeBbox(
  lat: number,
  lng: number,
  radiusKm: number,
): [number, number, number, number] {
  const latDelta = radiusKm / 111.0;
  const lngDelta = radiusKm / (111.0 * Math.cos((lat * Math.PI) / 180));
  return [lng - lngDelta, lat - latDelta, lng + lngDelta, lat + latDelta];
}

function bboxToGeoJSON(bbox: [number, number, number, number]) {
  const [w, s, e, n] = bbox;
  return {
    type: "Feature" as const,
    geometry: {
      type: "Polygon" as const,
      coordinates: [
        [
          [w, s],
          [e, s],
          [e, n],
          [w, n],
          [w, s],
        ],
      ],
    },
    properties: {},
  };
}

function LocationSelector({
  regionGeometry,
  onBboxChange,
}: {
  regionGeometry: GeofabrikFeature["geometry"];
  onBboxChange: (bbox: [number, number, number, number] | null) => void;
}) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<NominatimResult[]>([]);
  const [selected, setSelected] = useState<NominatimResult | null>(null);
  const [radius, setRadius] = useState(10);
  const [showDropdown, setShowDropdown] = useState(false);
  const [searching, setSearching] = useState(false);
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<import("maplibre-gl").Map | null>(null);
  const mapReadyRef = useRef(false);

  const onBboxChangeRef = useRef(onBboxChange);
  onBboxChangeRef.current = onBboxChange;

  const bbox = useMemo((): [number, number, number, number] | null => {
    if (!selected) return null;
    return computeBbox(parseFloat(selected.lat), parseFloat(selected.lon), radius);
  }, [selected, radius]);

  // Notify parent when bbox changes
  useEffect(() => {
    onBboxChangeRef.current(bbox);
  }, [bbox]);

  // 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)}&format=json&limit=6&addressdetails=0`,
          { headers: { "User-Agent": "Transportationer/1.0 (15-minute city analyzer)" } },
        );
        const data: NominatimResult[] = await res.json();
        setResults(data);
        setShowDropdown(true);
      } catch {
        setResults([]);
      } finally {
        setSearching(false);
      }
    }, 500);
    return () => clearTimeout(timer);
  }, [query]);

  // Apply bbox to mini map
  const applyBboxToMap = useCallback(
    (map: import("maplibre-gl").Map, b: [number, number, number, number] | null) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const src = map.getSource("area-bbox") as any;
      if (!src) return;
      if (b) {
        src.setData(bboxToGeoJSON(b));
        map.fitBounds([b[0], b[1], b[2], b[3]], { padding: 40, duration: 500 });
      } else {
        src.setData({ type: "FeatureCollection", features: [] });
      }
    },
    [],
  );

  // Update mini map when bbox changes
  useEffect(() => {
    const map = mapRef.current;
    if (map && mapReadyRef.current) applyBboxToMap(map, bbox);
  }, [bbox, applyBboxToMap]);

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

      // Register pmtiles protocol (idempotent)
      try {
        const p = new Protocol();
        mgl.addProtocol("pmtiles", p.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;

        // Region outline (Geofabrik polygon)
        map.addSource("area-region", {
          type: "geojson",
          data: regionGeometry
            ? { type: "Feature", geometry: regionGeometry, properties: {} }
            : { type: "FeatureCollection", features: [] },
        });
        map.addLayer({
          id: "area-region-fill",
          type: "fill",
          source: "area-region",
          paint: { "fill-color": "#64748b", "fill-opacity": 0.1 },
        });
        map.addLayer({
          id: "area-region-line",
          type: "line",
          source: "area-region",
          paint: { "line-color": "#64748b", "line-width": 1.5, "line-dasharray": [3, 2] },
        });

        // Selected sub-area bbox
        map.addSource("area-bbox", {
          type: "geojson",
          data: { type: "FeatureCollection", features: [] },
        });
        map.addLayer({
          id: "area-bbox-fill",
          type: "fill",
          source: "area-bbox",
          paint: { "fill-color": "#2563eb", "fill-opacity": 0.15 },
        });
        map.addLayer({
          id: "area-bbox-line",
          type: "line",
          source: "area-bbox",
          paint: { "line-color": "#2563eb", "line-width": 2 },
        });

        // Fit to region if available
        if (regionGeometry) {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const src = map.getSource("area-region") as any;
          if (src) {
            // Fit map to the region outline
            try {
              const coords: number[][] =
                regionGeometry.type === "Polygon"
                  ? regionGeometry.coordinates[0]
                  : regionGeometry.coordinates[0][0];
              const lngs = coords.map((c) => c[0]);
              const lats = coords.map((c) => c[1]);
              map.fitBounds(
                [Math.min(...lngs), Math.min(...lats), Math.max(...lngs), Math.max(...lats)],
                { padding: 20 },
              );
            } catch {
              /* ignore */
            }
          }
        }

        // Apply bbox if already set (e.g. after component re-render)
        if (bbox) applyBboxToMap(map, bbox);
      });
    })();

    return () => {
      cancelled = true;
      mapReadyRef.current = false;
      mapRef.current?.remove();
      mapRef.current = null;
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <div className="space-y-3">
      {/* Geocoder */}
      <div className="relative">
        <div className="flex gap-2 items-center">
          <input
            type="search"
            placeholder="Search for a city or location…"
            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>
              </button>
            ))}
          </div>
        )}
      </div>

      {/* Radius selector */}
      {selected && (
        <div className="flex items-center gap-3">
          <label className="text-sm text-gray-600 shrink-0">Radius:</label>
          <div className="flex gap-1.5 flex-wrap">
            {RADIUS_OPTIONS.map((r) => (
              <button
                key={r}
                type="button"
                onClick={() => setRadius(r)}
                className={`px-2.5 py-1 rounded-full text-xs font-medium border transition-colors ${
                  radius === r
                    ? "bg-brand-600 text-white border-brand-600"
                    : "bg-white text-gray-600 border-gray-300 hover:border-brand-400"
                }`}
              >
                {r} km
              </button>
            ))}
          </div>
        </div>
      )}

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

      {bbox && (
        <p className="text-xs text-green-700">
          ✓ Sub-region: {radius} km around {selected!.display_name.split(",")[0]} — bbox [{bbox.map((v) => v.toFixed(4)).join(", ")}]
        </p>
      )}
      {!selected && (
        <p className="text-xs text-gray-400">
          Search for a location to select a sub-region, or leave empty to use the entire dataset.
        </p>
      )}
    </div>
  );
}

// ─── Confirm step ─────────────────────────────────────────────────────────────

function ConfirmStep({
  region,
  onBack,
  onConfirm,
}: {
  region: GeofabrikFeature;
  onBack: () => void;
  onConfirm: (
    slug: string,
    name: string,
    countryCode: string,
    bbox: [number, number, number, number] | null,
  ) => Promise<void>;
}) {
  const defaultSlug = region.properties.id.replace(/\//g, "-");
  const [slug, setSlug] = useState(defaultSlug);
  const [name, setName] = useState(region.properties.name);
  const [countryCode, setCountryCode] = useState("");
  const [bbox, setBbox] = useState<[number, number, number, number] | null>(null);
  const [loading, setLoading] = useState(false);

  const handleBboxChange = useCallback(
    (b: [number, number, number, number] | null) => setBbox(b),
    [],
  );

  return (
    <div className="card max-w-2xl">
      <h2 className="text-lg font-semibold mb-4">Confirm City Details</h2>

      <div className="space-y-4">
        <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)}
              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, "-"))
              }
              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="w-24">
          <label className="block text-sm font-medium text-gray-700 mb-1">
            Country Code <span className="text-gray-400">(2-letter)</span>
          </label>
          <input
            value={countryCode}
            maxLength={2}
            onChange={(e) => setCountryCode(e.target.value.toUpperCase())}
            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"
            placeholder="DE"
          />
        </div>

        {/* Sub-region selector */}
        <div>
          <label className="block text-sm font-medium text-gray-700 mb-2">
            Sub-region{" "}
            <span className="text-gray-400 font-normal">(optional — clip to a city area)</span>
          </label>
          <p className="text-xs text-gray-500 mb-3">
            Select a city center and radius to ingest only that sub-area of the dataset.
            Recommended for large regions (e.g. entire states). Affects OSM import,
            routing tiles, and grid analysis.
          </p>
          <LocationSelector
            regionGeometry={region.geometry}
            onBboxChange={handleBboxChange}
          />
        </div>

        <div className="bg-gray-50 rounded-md p-3">
          <p className="text-xs text-gray-500 font-medium mb-1">Source URL</p>
          <p className="text-xs text-gray-700 break-all">
            {region.properties.urls.pbf}
          </p>
        </div>
      </div>

      <div className="flex gap-3 mt-6">
        <button onClick={onBack} className="btn-secondary">
          Back
        </button>
        <button
          onClick={async () => {
            setLoading(true);
            await onConfirm(slug, name, countryCode, bbox);
            setLoading(false);
          }}
          disabled={loading || !slug || !name}
          className="btn-primary"
        >
          {loading ? "Starting…" : "Start Ingestion"}
        </button>
      </div>
    </div>
  );
}

// ─── Progress step ────────────────────────────────────────────────────────────

function ProgressStep({ jobId }: { jobId: string | null }) {
  const { stages, overall, error } = useJobProgress(jobId);

  return (
    <div className="card max-w-lg">
      <h2 className="text-lg font-semibold mb-6">Processing City Data</h2>

      <ol className="space-y-4">
        {stages.map((stage) => (
          <li key={stage.key} className="flex items-start gap-3">
            <StageIcon status={stage.status} />
            <div className="flex-1 min-w-0">
              <p className="text-sm font-medium text-gray-900">{stage.label}</p>
              {stage.status === "active" && (
                <>
                  <div className="w-full bg-gray-200 rounded-full h-1.5 mt-1.5">
                    <div
                      className="bg-brand-600 h-1.5 rounded-full transition-all duration-500"
                      style={{ width: `${stage.pct}%` }}
                    />
                  </div>
                  <p className="text-xs text-gray-500 mt-1 truncate">
                    {stage.message}
                  </p>
                </>
              )}
              {stage.status === "failed" && error && (
                <p className="text-xs text-red-600 mt-1">{error}</p>
              )}
            </div>
          </li>
        ))}
      </ol>

      {overall === "completed" && (
        <div className="mt-6 p-4 bg-green-50 rounded-lg text-green-800 text-sm">
          ✓ City ingestion complete!{" "}
          <a href="/admin" className="underline font-medium">
            Return to dashboard
          </a>{" "}
          or{" "}
          <a href="/" className="underline font-medium">
            view on map
          </a>
          .
        </div>
      )}

      {overall === "failed" && (
        <div className="mt-6 p-4 bg-red-50 rounded-lg text-red-800 text-sm">
          ✗ Ingestion failed: {error}.{" "}
          <a href="/admin" className="underline">
            Return to dashboard
          </a>
          .
        </div>
      )}
    </div>
  );
}

function StageIcon({ status }: { status: StageStatus["status"] }) {
  if (status === "completed")
    return (
      <span className="w-5 h-5 flex items-center justify-center rounded-full bg-green-100 text-green-600 text-xs mt-0.5 shrink-0">
        ✓
      </span>
    );
  if (status === "failed")
    return (
      <span className="w-5 h-5 flex items-center justify-center rounded-full bg-red-100 text-red-600 text-xs mt-0.5 shrink-0">
        ✗
      </span>
    );
  if (status === "active")
    return (
      <span className="w-5 h-5 flex items-center justify-center mt-0.5 shrink-0">
        <svg
          className="animate-spin w-4 h-4 text-brand-600"
          viewBox="0 0 24 24"
          fill="none"
        >
          <circle
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            strokeWidth="2"
            opacity="0.25"
          />
          <path
            d="M12 2a10 10 0 0 1 10 10"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
          />
        </svg>
      </span>
    );
  return (
    <span className="w-5 h-5 rounded-full border-2 border-gray-300 mt-0.5 shrink-0" />
  );
}

import type { StageStatus } from "@/hooks/use-job-progress";

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

export default function AddCityPage() {
  const [step, setStep] = useState<Step>("browse");
  const [selected, setSelected] = useState<GeofabrikFeature | null>(null);
  const [jobId, setJobId] = useState<string | null>(null);
  const [ingestError, setIngestError] = useState<string | null>(null);

  const handleConfirm = async (
    slug: string,
    name: string,
    countryCode: string,
    bbox: [number, number, number, number] | null,
  ) => {
    setIngestError(null);
    try {
      const res = await fetch("/api/admin/cities", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          slug,
          name,
          countryCode,
          geofabrikUrl: selected!.properties.urls.pbf,
          ...(bbox ? { bbox } : {}),
        }),
      });
      if (!res.ok) {
        const data = await res.json();
        setIngestError(data.error ?? "Failed to start ingestion");
        return;
      }
      const data = await res.json();
      setJobId(data.jobId);
      setStep("ingest");
    } catch (e) {
      setIngestError(String(e));
    }
  };

  return (
    <div className="max-w-3xl 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">
        Select an OpenStreetMap region to import for 15-minute city analysis.
      </p>

      <StepIndicator current={step} />

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

      {step === "browse" && (
        <GeofabrikBrowser
          onSelect={(f) => {
            setSelected(f);
            setStep("confirm");
          }}
        />
      )}
      {step === "confirm" && selected && (
        <ConfirmStep
          region={selected}
          onBack={() => setStep("browse")}
          onConfirm={handleConfirm}
        />
      )}
      {step === "ingest" && <ProgressStep jobId={jobId} />}
    </div>
  );
}
