import React, { useEffect, useRef, useState, useCallback, useId } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import type { MapMouseEvent, Map as MapLibreMap, Marker as MapLibreMarker } from 'maplibre-gl';
import { getDigiPin, getLatLngFromDigiPin, formatDigiPin, isWithinIndia } from '../core/digipin';
import { autocompleteAddress, looksLikeDigiPin, couldBeDigiPinInput } from '../core/api';
import type { AutocompleteSuggestion } from '../core/api';
import { useGeolocation } from '../hooks/useGeolocation';
import type { MapPinSelectorProps } from '../core/types';
import {
  DEFAULT_MAP_STYLE_URL,
  waitForExternalMapRuntime,
  type MapRuntime,
} from '../core/map-runtime';

// ─── Constants ───────────────────────────────────────────────────────────────

/** India's geographic center – shown when no default location is given */
const INDIA_CENTER = { lat: 13.004270, lng: 77.589291 };
const MIN_ZOOM     = 6;   // users cannot zoom out beyond a useful India-level view
const OVERVIEW_ZOOM = 9;
const STREET_ZOOM = 18;

type MapLoadState = 'loading' | 'ready' | 'unavailable' | 'error';

// ─── Pin dimensions ───────────────────────────────────────────────────────────
// MUST match the SVG width/height exactly.
// MapLibre reads element.offsetWidth/offsetHeight to compute `anchor:'bottom'`.
// The pin tip lives at SVG coordinate (20, 52) = bottom-center of the element.
const PIN_W = 40;
const PIN_H = 52;

// ─── Marker helper ───────────────────────────────────────────────────────────

function computeDigiPinSafe(la: number, lo: number): string | null {
  if (!isWithinIndia(la, lo)) return null;
  try {
    return getDigiPin(la, lo);
  } catch {
    return null;
  }
}

const DEFAULT_BRAND = '#0ea5e9';

function createPinElement(brandColor: string = DEFAULT_BRAND): HTMLDivElement {
  const el = document.createElement('div');
  el.className = 'qr-pin';
  el.setAttribute('aria-label', 'Drag to adjust your location');

  // ─ Set explicit inline dimensions ─────────────────────────────────────────
  // This is critical: without them, the browser may add inline-baseline
  // whitespace below the <svg> tag, making offsetHeight > PIN_H and causing
  // the anchor='bottom' point to land below the actual pin tip.
  el.style.width  = `${PIN_W}px`;
  el.style.height = `${PIN_H}px`;

  el.innerHTML = `
    <svg
      class="qr-pin__svg"
      width="${PIN_W}"
      height="${PIN_H}"
      viewBox="0 0 ${PIN_W} ${PIN_H}"
      overflow="visible"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
    >
      <defs>
        <!--
          The filter region must be generous enough to contain the shadow blur.
          stdDeviation=3 → ~9px blur radius; our region extends 50% on each side.
          overflow="visible" on the <svg> lets it paint outside the viewport.
        -->
        <filter id="qr-pin-shadow" x="-60%" y="-40%" width="220%" height="220%">
          <feDropShadow dx="0" dy="3" stdDeviation="3.5"
            flood-color="#000" flood-opacity="0.28"/>
        </filter>
      </defs>

      <!-- Teardrop body — tip is at (20, 52) = bottom-center of the SVG -->
      <path
        d="M20 0C9.402 0 0 9.402 0 20C0 34 20 52 20 52S40 34 40 20C40 9.402 30.598 0 20 0Z"
        fill="${brandColor}"
        filter="url(#qr-pin-shadow)"
      />
      <!-- Dot inside the pin -->
      <circle cx="20" cy="20" r="9" fill="white"/>
      <circle cx="20" cy="20" r="5" fill="${brandColor}"/>
    </svg>

    <!--
      Pulse ring — position: absolute inside .qr-pin (position: relative).
      bottom: -(height/2)  →  bottom: -10px  centres the 20×20 circle
      exactly at y = PIN_H = 52px = the pin tip coordinate.
    -->
    <div class="qr-pin__pulse" aria-hidden="true"></div>
  `;
  return el;
}

function collapseCompactAttribution(container: HTMLElement | null): void {
  const attribEl = container?.querySelector(
    '.maplibregl-ctrl-attrib.maplibregl-compact'
  );
  if (attribEl instanceof HTMLElement) {
    attribEl.classList.remove('maplibregl-compact-show');
    attribEl.setAttribute('open', '');
  }
}

function initMapWithRuntime(
  runtime: MapRuntime,
  container: HTMLDivElement,
  markerRef: React.MutableRefObject<MapLibreMarker | null>,
  options: {
    styleUrl: string;
    initLat: number;
    initLng: number;
    initZoom: number;
    brandColor: string;
    indiaBoundaryUrl?: string;
    refreshDigipin: (la: number, lo: number) => void;
    onReady: () => void;
    onError: (message: string) => void;
  }
): { map: MapLibreMap; cleanup: () => void } {
  const {
    styleUrl,
    initLat,
    initLng,
    initZoom,
    brandColor,
    indiaBoundaryUrl,
    refreshDigipin,
    onReady,
    onError,
  } = options;

  const boundaryPromise: Promise<unknown> = indiaBoundaryUrl
    ? fetch(indiaBoundaryUrl)
        .then((r) => (r.ok ? r.json() : null))
        .catch(() => null)
    : Promise.resolve(null);

  const map = new runtime.Map({
    container,
    style: styleUrl,
    center: [initLng, initLat],
    zoom: initZoom,
    minZoom: MIN_ZOOM,
    attributionControl: false,
    touchZoomRotate: true,
  });

  if (typeof runtime.AttributionControl === 'function') {
    map.addControl(
      new runtime.AttributionControl({
        compact: true,
        customAttribution:
          '© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a> contributors &nbsp;© <a href="https://carto.com/attributions" target="_blank" rel="noopener">CARTO</a>',
      }),
      'bottom-right'
    );
  }

  if (typeof runtime.NavigationControl === 'function') {
    map.addControl(
      new runtime.NavigationControl({ showCompass: false }),
      'top-right'
    );
  }

  let marker: MapLibreMarker | null = null;

  map.on('load', () => {
    collapseCompactAttribution(container);
    onReady();

    boundaryPromise.then((geoJson) => {
      if (!geoJson || !map.isStyleLoaded()) return;
      try {
        map.addSource('india-boundary', {
          type: 'geojson',
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          data: geoJson as any,
        });
        map.addLayer({
          id: 'india-boundary-line',
          type: 'line',
          source: 'india-boundary',
          paint: {
            'line-color': '#94a3b8',
            'line-width': 1.5,
            'line-opacity': 0.65,
          },
        });
      } catch {
        // Silent: duplicate source/layer on hot-reload is not an error
      }
    });

    marker = new runtime.Marker({
      element: createPinElement(brandColor),
      draggable: true,
      anchor: 'bottom',
    })
      .setLngLat([initLng, initLat])
      .addTo(map);

    markerRef.current = marker;

    marker.on('drag', () => {
      const pos = marker!.getLngLat();
      refreshDigipin(pos.lat, pos.lng);
    });

    marker.on('dragend', () => {
      const pos = marker!.getLngLat();
      map.easeTo({ center: [pos.lng, pos.lat], duration: 250 });
    });
  });

  map.on('error', () => {
    onError('Map tiles could not load. Please try again.');
  });

  map.on('click', (e: MapMouseEvent) => {
    const pos = e.lngLat;
    marker?.setLngLat([pos.lng, pos.lat]);
    refreshDigipin(pos.lat, pos.lng);
    map.easeTo({ center: [pos.lng, pos.lat], duration: 200 });
  });

  return {
    map,
    cleanup: () => {
      markerRef.current = null;
      marker = null;
      map.remove();
    },
  };
}

// ─── Component ───────────────────────────────────────────────────────────────

const MapPinSelector: React.FC<MapPinSelectorProps> = ({
  onLocationConfirm,
  defaultLat,
  defaultLng,
  mapHeight = '380px',
  theme: _theme = 'light',
  indiaBoundaryUrl,
  enableSearch = true,
  apiKey,
  apiBaseUrl,
  brandColor = DEFAULT_BRAND,
  mapStyle = DEFAULT_MAP_STYLE_URL,
  embedMode = 'bundled',
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<MapLibreMap | null>(null);
  const markerRef = useRef<MapLibreMarker | null>(null);

  const initLat = defaultLat ?? INDIA_CENTER.lat;
  const initLng = defaultLng ?? INDIA_CENTER.lng;
  const hasDefault = defaultLat !== undefined && defaultLng !== undefined;

  const [lat, setLat] = useState(initLat);
  const [lng, setLng] = useState(initLng);
  const [digipin, setDigipin] = useState<string | null>(() =>
    computeDigiPinSafe(initLat, initLng)
  );
  const [mapLoaded, setMapLoaded] = useState(false);
  const [mapState, setMapState] = useState<MapLoadState>('loading');
  const [mapError, setMapError] = useState<string | null>(null);

  // ── Search state ─────────────────────────────────────────────────────────
  const [searchQuery, setSearchQuery] = useState('');
  const [suggestions, setSuggestions] = useState<AutocompleteSuggestion[]>([]);
  const [searchLoading, setSearchLoading] = useState(false);
  const [searchError, setSearchError] = useState<string | null>(null);
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const searchInputRef = useRef<HTMLInputElement>(null);
  const listboxId = useId();

  const { loading: geoLoading, error: geoError, locate, clearError } = useGeolocation();

  const refreshDigipin = useCallback((la: number, lo: number) => {
    setLat(la);
    setLng(lo);
    setDigipin(computeDigiPinSafe(la, lo));
  }, []);

  // ── Fly map + move pin to a coord ────────────────────────────────────────
  const flyToCoord = useCallback((la: number, lo: number) => {
    refreshDigipin(la, lo);
    mapRef.current?.flyTo({ center: [lo, la], zoom: STREET_ZOOM, duration: 900, essential: true });
    markerRef.current?.setLngLat([lo, la]);
  }, [refreshDigipin]);

  // ── Search input change handler ──────────────────────────────────────────
  const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setSearchQuery(value);
    setSearchError(null);

    if (debounceRef.current) clearTimeout(debounceRef.current);

    const trimmed = value.trim();

    if (!trimmed) {
      setSuggestions([]);
      setDropdownOpen(false);
      setSearchLoading(false);
      return;
    }

    if (looksLikeDigiPin(trimmed)) {
      try {
        const coords = getLatLngFromDigiPin(trimmed);
        const la = parseFloat(coords.latitude);
        const lo = parseFloat(coords.longitude);
        flyToCoord(la, lo);
        setSuggestions([]);
        setDropdownOpen(false);
        setSearchQuery(formatDigiPin(trimmed));
      } catch {
        setSearchError('Invalid DigiPin code');
      }
      return;
    }

    if (couldBeDigiPinInput(trimmed)) {
      setSuggestions([]);
      setDropdownOpen(false);
      setSearchLoading(false);
      return;
    }

    if (trimmed.length < 3) {
      setSuggestions([]);
      setDropdownOpen(false);
      return;
    }

    setSearchLoading(true);
    debounceRef.current = setTimeout(async () => {
      try {
        const key = apiKey ?? '';
        const results = await autocompleteAddress(trimmed, key, apiBaseUrl, 5, lat, lng);
        setSuggestions(results);
        setDropdownOpen(results.length > 0);
      } catch (err) {
        setSearchError(err instanceof Error ? err.message : 'Search failed');
        setSuggestions([]);
        setDropdownOpen(false);
      } finally {
        setSearchLoading(false);
      }
    }, 400);
  }, [apiKey, apiBaseUrl, flyToCoord, lat, lng]);

  const handleSuggestionSelect = useCallback((s: AutocompleteSuggestion) => {
    flyToCoord(s.coordinates.latitude, s.coordinates.longitude);
    setSearchQuery('');
    setSuggestions([]);
    setDropdownOpen(false);
    searchInputRef.current?.blur();
  }, [flyToCoord]);

  const handleSearchKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Escape') {
      setSuggestions([]);
      setDropdownOpen(false);
      setSearchQuery('');
    }
  }, []);

  // ── Map initialization ────────────────────────────────────────────────────
  useEffect(() => {
    if (!containerRef.current) return;

    let cancelled = false;
    let cancelWait: (() => void) | null = null;
    let mapCleanup: (() => void) | null = null;

    const initZoom = hasDefault ? STREET_ZOOM : OVERVIEW_ZOOM;
    const container = containerRef.current;

    const mountMap = (runtime: MapRuntime) => {
      if (cancelled || !containerRef.current) return;

      try {
        const { map, cleanup } = initMapWithRuntime(runtime, container, markerRef, {
          styleUrl: mapStyle,
          initLat,
          initLng,
          initZoom,
          brandColor,
          indiaBoundaryUrl,
          refreshDigipin,
          onReady: () => {
            if (cancelled) return;
            setMapLoaded(true);
            setMapState('ready');
            setMapError(null);
          },
          onError: (message) => {
            if (cancelled) return;
            setMapState((state) => (state === 'ready' ? state : 'error'));
            setMapError(message);
          },
        });

        mapRef.current = map;
        mapCleanup = cleanup;
      } catch (err) {
        setMapState('error');
        setMapError(err instanceof Error ? err.message : 'Map failed to initialize.');
      }
    };

    const handleUnavailable = () => {
      if (cancelled) return;
      setMapState('unavailable');
      setMapError('Map could not load. Check your connection and try again.');
    };

    if (embedMode === 'external') {
      cancelWait = waitForExternalMapRuntime(mountMap, handleUnavailable);
    } else {
      mountMap(maplibregl as MapRuntime);
    }

    return () => {
      cancelled = true;
      cancelWait?.();
      if (debounceRef.current) clearTimeout(debounceRef.current);
      markerRef.current = null;
      mapRef.current = null;
      mapCleanup?.();
    };
  }, [
    brandColor,
    embedMode,
    hasDefault,
    indiaBoundaryUrl,
    initLat,
    initLng,
    mapStyle,
    refreshDigipin,
  ]);

  // ── Geolocation handler ───────────────────────────────────────────────────
  const handleLocateMe = useCallback(() => {
    clearError();
    locate((userLat, userLng) => {
      refreshDigipin(userLat, userLng);
      mapRef.current?.flyTo({
        center: [userLng, userLat],
        zoom: STREET_ZOOM,
        duration: 1400,
        essential: true,
      });
      markerRef.current?.setLngLat([userLng, userLat]);
    });
  }, [locate, clearError, refreshDigipin]);

  const handleConfirm = useCallback(() => {
    if (digipin) {
      onLocationConfirm(lat, lng, digipin);
    }
  }, [lat, lng, digipin, onLocationConfirm]);

  const isInIndia = digipin !== null;
  const canConfirm = isInIndia && mapLoaded && mapState === 'ready';
  const showMapStatus = mapState !== 'ready';

  return (
    <div className="qr-map-wrapper">
      <div className="qr-step-header">
        <div className="qr-step-badge">1</div>
        <div className="qr-step-text">
          <span className="qr-step-title">Pin Your Location</span>
          <span className="qr-step-sub">
            Tap the map or drag the pin to your exact home / office
          </span>
        </div>
      </div>

      {enableSearch && (
        <div className="qr-search-bar" role="search">
          <div className="qr-search-bar__input-wrap">
            <svg
              className="qr-search-bar__icon"
              aria-hidden="true"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
            >
              <circle cx="11" cy="11" r="8" />
              <path d="M21 21l-4.35-4.35" />
            </svg>

            <input
              ref={searchInputRef}
              type="search"
              className="qr-search-bar__input"
              placeholder="Search address, 'pharmacy near me', or DigiPin…"
              value={searchQuery}
              onChange={handleSearchChange}
              onKeyDown={handleSearchKeyDown}
              autoComplete="off"
              autoCorrect="off"
              autoCapitalize="characters"
              spellCheck={false}
              aria-label="Search address or DigiPin"
              aria-autocomplete="list"
              aria-controls={dropdownOpen ? listboxId : undefined}
              aria-expanded={dropdownOpen}
            />

            {searchLoading && (
              <span className="qr-search-bar__spinner qr-spinner qr-spinner--sm" aria-hidden="true" />
            )}

            {searchQuery && !searchLoading && (
              <button
                type="button"
                className="qr-search-bar__clear"
                onClick={() => { setSearchQuery(''); setSuggestions([]); setDropdownOpen(false); setSearchError(null); }}
                aria-label="Clear search"
                tabIndex={-1}
              >
                ×
              </button>
            )}
          </div>

          {searchError && (
            <p className="qr-search-bar__error" role="alert">{searchError}</p>
          )}

          {dropdownOpen && suggestions.length > 0 && (
            <ul
              id={listboxId}
              className="qr-search-bar__dropdown"
              role="listbox"
              aria-label="Address suggestions"
            >
              {suggestions.map((s, i) => (
                <li
                  key={i}
                  className="qr-search-bar__suggestion"
                  role="option"
                  aria-selected="false"
                  onMouseDown={(e) => {
                    e.preventDefault();
                    handleSuggestionSelect(s);
                  }}
                >
                  <svg
                    className="qr-search-bar__suggestion-icon"
                    aria-hidden="true"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="1.8"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  >
                    <path d="M12 2C8.134 2 5 5.134 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.866-3.134-7-7-7z" />
                    <circle cx="12" cy="9" r="2.5" />
                  </svg>
                  <div className="qr-search-bar__suggestion-text">
                    <span className="qr-search-bar__suggestion-name">{s.displayName}</span>
                    {s.address && s.address !== s.displayName && (
                      <span className="qr-search-bar__suggestion-sub">{s.address}</span>
                    )}
                  </div>
                </li>
              ))}
            </ul>
          )}

          <p className="qr-search-bar__hint">
            <span className="qr-search-bar__hint-chip">DigiPin</span>
            Type your DigiPin code for instant offline lookup — no API call
          </p>
        </div>
      )}

      <div className="qr-map-outer" style={{ height: mapHeight }}>
        <div ref={containerRef} className="qr-map-canvas" />

        {showMapStatus && (
          <div className="qr-map-notice" role={mapState === 'loading' ? 'status' : 'alert'}>
            {mapState === 'loading' ? (
              <>
                <span className="qr-spinner qr-spinner--sm" aria-hidden="true" />
                <span>Loading map…</span>
              </>
            ) : (
              mapError ?? 'Map is unavailable. Please try again.'
            )}
          </div>
        )}

        {digipin && (
          <div className="qr-digipin-badge" aria-live="polite" aria-label={`DigiPin: ${digipin}`}>
            <span className="qr-digipin-badge__label">DigiPin</span>
            <span className="qr-digipin-badge__code">{digipin}</span>
          </div>
        )}

        {!isInIndia && mapLoaded && (
          <div className="qr-map-notice" role="status">
            Move map to India to get a DigiPin
          </div>
        )}

        <button
          className={`qr-locate-btn${geoLoading ? ' qr-locate-btn--loading' : ''}`}
          onClick={handleLocateMe}
          disabled={geoLoading || mapState !== 'ready'}
          title="Use my current location"
          aria-label="Use my current location"
          type="button"
        >
          {geoLoading ? (
            <span className="qr-spinner" aria-hidden="true" />
          ) : (
          <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <circle cx="12" cy="12" r="3" fill="currentColor" stroke="none" />
            <circle cx="12" cy="12" r="7" />
            <path d="M12 2v3M12 19v3M2 12h3M19 12h3" />
          </svg>
          )}
        </button>

        {geoError && (
          <div className="qr-geo-error" role="alert">
            <span>{geoError}</span>
            <button
              type="button"
              className="qr-geo-error__dismiss"
              onClick={clearError}
              aria-label="Dismiss error"
            >
              ×
            </button>
          </div>
        )}
      </div>

      {mapLoaded && (
        <div className="qr-coords-strip" aria-label="Current pin coordinates">
          <span>{lat.toFixed(5)}° N</span>
          <span className="qr-coords-sep">·</span>
          <span>{lng.toFixed(5)}° E</span>
        </div>
      )}

      <div className="qr-map-actions">
        <button
          type="button"
          className="qr-btn qr-btn--primary qr-btn--full"
          onClick={handleConfirm}
          disabled={!canConfirm}
        >
          Confirm Location
          <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2.5"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M5 12h14M12 5l7 7-7 7" />
          </svg>
        </button>
        {!isInIndia && mapLoaded && (
          <p className="qr-map-hint">Move the pin to a location in India to continue</p>
        )}
        {mapState !== 'ready' && (
          <p className="qr-map-hint">
            {mapState === 'loading'
              ? 'Loading the map before location confirmation.'
              : 'Map is unavailable, so location confirmation is paused.'}
          </p>
        )}
      </div>
    </div>
  );
};

export default MapPinSelector;
