'use client';

/* eslint-disable import-x/no-named-as-default-member */
import mapboxgl from 'mapbox-gl';
import { MapPin, Search, X } from 'lucide-react';
import * as React from 'react';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useMapboxStyle } from '@/lib/mapbox-style';
import { cn } from '@/lib/utils';

/**
 * Lightweight Mapbox-based location picker. Click the map (or drag the
 * marker) to set lat/lng; optional search box geocodes via Mapbox Places
 * (Vietnam-scoped). Emits both the new coords and, when available, the
 * parsed address components from the geocoder feature context — consumers
 * can hydrate address/ward/district/city fields without extra typing.
 */
export interface ResolvedAddress {
  address?: string;
  ward?: string;
  district?: string;
  city?: string;
}

interface LocationPickerProps {
  lat?: number | null;
  lng?: number | null;
  onChange: (coords: { lat: number; lng: number }, resolved?: ResolvedAddress) => void;
  height?: string;
  className?: string;
}

const DEFAULT_CENTER: [number, number] = [106.7009, 10.7769]; // HCMC
const DEFAULT_ZOOM = 12;
const PICKED_ZOOM = 15;

interface MapboxFeature {
  id: string;
  place_name: string;
  text: string;
  center: [number, number];
  place_type?: string[];
  context?: Array<{ id: string; text: string }>;
}

function parseContext(feature: MapboxFeature): ResolvedAddress {
  const ctx = feature.context ?? [];
  const out: ResolvedAddress = {};
  // `feature.text` is the matched leaf (street / POI / locality).
  out.address = feature.place_name;
  for (const c of ctx) {
    if (c.id.startsWith('locality')) out.ward = c.text;
    else if (c.id.startsWith('district')) out.district = c.text;
    else if (c.id.startsWith('place')) {
      // Mapbox uses "place" for cities in VN
      out.city = c.text;
    } else if (c.id.startsWith('region') && !out.city) {
      out.city = c.text;
    }
  }
  return out;
}

export function LocationPicker({
  lat,
  lng,
  onChange,
  height = '320px',
  className,
}: LocationPickerProps) {
  const mapContainerRef = React.useRef<HTMLDivElement>(null);
  const mapRef = React.useRef<mapboxgl.Map | null>(null);
  const markerRef = React.useRef<mapboxgl.Marker | null>(null);
  const onChangeRef = React.useRef(onChange);
  const mapStyle = useMapboxStyle();

  // Keep the latest onChange in a ref so our marker/map listeners don't
  // trigger a re-initialisation every render.
  React.useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);

  const token = process.env['NEXT_PUBLIC_MAPBOX_TOKEN'];

  // Init map + marker once.
  React.useEffect(() => {
    if (!mapContainerRef.current || !token) return;
    mapboxgl.accessToken = token;

    const initial: [number, number] =
      typeof lat === 'number' && typeof lng === 'number' ? [lng, lat] : DEFAULT_CENTER;
    const initialZoom =
      typeof lat === 'number' && typeof lng === 'number' ? PICKED_ZOOM : DEFAULT_ZOOM;

    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: mapStyle,
      center: initial,
      zoom: initialZoom,
      attributionControl: false,
    });
    map.addControl(new mapboxgl.NavigationControl(), 'top-right');
    map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');

    // Draggable primary marker.
    const markerEl = document.createElement('div');
    markerEl.style.cssText = `width: 32px; height: 32px; cursor: grab;`;
    const pin = document.createElement('div');
    pin.style.cssText = `
      width: 100%; height: 100%;
      border-radius: 50% 50% 50% 0;
      background: hsl(var(--primary));
      border: 3px solid hsl(var(--card));
      box-shadow: 0 2px 6px rgba(0,0,0,0.35);
      transform: rotate(-45deg);
      pointer-events: none;
    `;
    markerEl.appendChild(pin);

    const marker = new mapboxgl.Marker({ element: markerEl, draggable: true, anchor: 'bottom' })
      .setLngLat(initial)
      .addTo(map);

    marker.on('dragend', () => {
      const lngLat = marker.getLngLat();
      onChangeRef.current({ lat: lngLat.lat, lng: lngLat.lng });
    });

    map.on('click', (e) => {
      marker.setLngLat(e.lngLat);
      onChangeRef.current({ lat: e.lngLat.lat, lng: e.lngLat.lng });
    });

    mapRef.current = map;
    markerRef.current = marker;

    return () => {
      marker.remove();
      map.remove();
      mapRef.current = null;
      markerRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Sync theme.
  React.useEffect(() => {
    mapRef.current?.setStyle(mapStyle);
  }, [mapStyle]);

  // When parent updates lat/lng externally (e.g. hydrating the edit form),
  // fly the map and move the marker.
  React.useEffect(() => {
    const map = mapRef.current;
    const marker = markerRef.current;
    if (!map || !marker) return;
    if (typeof lat !== 'number' || typeof lng !== 'number') return;
    const current = marker.getLngLat();
    if (Math.abs(current.lat - lat) < 1e-7 && Math.abs(current.lng - lng) < 1e-7) return;
    marker.setLngLat([lng, lat]);
    map.flyTo({ center: [lng, lat], zoom: Math.max(map.getZoom(), PICKED_ZOOM - 1) });
  }, [lat, lng]);

  // Geocoding search.
  const [query, setQuery] = React.useState('');
  const [results, setResults] = React.useState<MapboxFeature[]>([]);
  const [searching, setSearching] = React.useState(false);
  const abortRef = React.useRef<AbortController | null>(null);

  React.useEffect(() => {
    if (!token) return;
    const trimmed = query.trim();
    if (trimmed.length < 3) {
      setResults([]);
      return;
    }
    const handle = setTimeout(async () => {
      abortRef.current?.abort();
      const ctrl = new AbortController();
      abortRef.current = ctrl;
      setSearching(true);
      try {
        const url =
          `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(trimmed)}.json` +
          `?access_token=${token}&country=vn&language=vi&limit=5`;
        const res = await fetch(url, { signal: ctrl.signal });
        if (!res.ok) throw new Error(String(res.status));
        const body = (await res.json()) as { features?: MapboxFeature[] };
        setResults(body.features ?? []);
      } catch {
        if (!ctrl.signal.aborted) setResults([]);
      } finally {
        if (!ctrl.signal.aborted) setSearching(false);
      }
    }, 350);
    return () => clearTimeout(handle);
  }, [query, token]);

  const pickResult = (feature: MapboxFeature) => {
    const [fLng, fLat] = feature.center;
    markerRef.current?.setLngLat([fLng, fLat]);
    mapRef.current?.flyTo({ center: [fLng, fLat], zoom: PICKED_ZOOM });
    onChangeRef.current({ lat: fLat, lng: fLng }, parseContext(feature));
    setQuery('');
    setResults([]);
  };

  if (!token) {
    return (
      <div
        className={cn(
          'flex items-center justify-center rounded-lg border border-dashed bg-muted text-sm text-muted-foreground',
          className,
        )}
        style={{ height }}
      >
        Thiết lập NEXT_PUBLIC_MAPBOX_TOKEN để chọn vị trí trên bản đồ
      </div>
    );
  }

  return (
    <div className={cn('relative overflow-hidden rounded-lg border', className)}>
      <div ref={mapContainerRef} style={{ height }} className="w-full" />

      {/* Search box */}
      <div className="absolute left-3 top-3 z-10 w-[min(360px,calc(100%-1.5rem))]">
        <div className="relative">
          <Search className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" aria-hidden="true" />
          <input
            type="text"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Tìm địa chỉ hoặc địa điểm..."
            className="h-9 w-full rounded-md border bg-card pl-8 pr-8 text-sm text-card-foreground shadow-sm outline-none focus:ring-2 focus:ring-primary/30"
            aria-label="Tìm địa chỉ trên bản đồ"
          />
          {query && (
            <button
              type="button"
              onClick={() => {
                setQuery('');
                setResults([]);
              }}
              aria-label="Xoá tìm kiếm"
              className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
            >
              <X className="h-3.5 w-3.5" aria-hidden="true" />
            </button>
          )}
        </div>
        {results.length > 0 && (
          <ul className="mt-1 max-h-60 overflow-y-auto rounded-md border bg-card text-sm shadow-lg">
            {results.map((f) => (
              <li key={f.id}>
                <button
                  type="button"
                  onClick={() => pickResult(f)}
                  className="flex w-full items-start gap-2 px-3 py-2 text-left hover:bg-accent hover:text-accent-foreground"
                >
                  <MapPin className="mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" aria-hidden="true" />
                  <span className="line-clamp-2">{f.place_name}</span>
                </button>
              </li>
            ))}
          </ul>
        )}
        {searching && query.trim().length >= 3 && results.length === 0 && (
          <p className="mt-1 rounded bg-card/90 px-2 py-1 text-xs text-muted-foreground shadow">
            Đang tìm...
          </p>
        )}
      </div>

      {/* Hint badge */}
      <div className="pointer-events-none absolute bottom-3 left-3 rounded bg-card/90 px-2 py-1 text-xs text-muted-foreground shadow">
        Nhấp vào bản đồ hoặc kéo pin để chọn vị trí
      </div>
    </div>
  );
}
