'use client';
import { FunctionComponent, useEffect, useRef, useState } from 'react';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Paper from '@mui/material/Paper';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import AppIcon from '@/components/common/AppIcon';
import { NESHAN_TILE_ATTRIBUTION, NESHAN_TILE_URL_TEMPLATE, NESHAN_DEFAULT_ZOOM } from '@/services/geography/constants';
import { searchNeshan, reverseGeocodeNeshan, type NeshanSearchResult } from '@/services/geography/neshan';
import type { LatLng } from '@/services/addresses/types';

export interface NeshanMapProps {
  value: LatLng | null;
  onChange: (value: LatLng) => void;
  center: LatLng;
  neshanKey: string;
  searchPlaceholder: string;
  locateMeLabel: string;
  pinPreviewPrefix: string;
  resolvingLabel: string;
  error?: boolean;
  errorText?: string;
}

// A brand-colored teardrop pin as a DivIcon (never Leaflet's default marker images — those need
// bundler-specific asset-path patching that a DivIcon sidesteps entirely, and this keeps the pin
// visually identical to the grid fallback's `AppIcon icon="location"`).
const PIN_HTML =
  '<svg width="32" height="32" viewBox="0 0 24 24" fill="var(--bal-primary)" style="filter: drop-shadow(0 1px 2px var(--bal-pin-shadow))"><path d="M12 2C7.58 2 4 5.58 4 10c0 5.25 7 12 8 12s8-6.75 8-12c0-4.42-3.58-8-8-8zm0 10.5A2.5 2.5 0 1 1 12 7a2.5 2.5 0 0 1 0 5.5z"/></svg>';

/**
 * Real Neshan-tiled map (Leaflet), dynamically imported with `ssr: false` by `AddressMapPicker`
 * when `NEXT_PUBLIC_NESHAN_KEY` is set. Address search box (Neshan geocode), locate-me (browser
 * GPS), a draggable/tappable pin, and a reverse-geocoded preview line — coordinates are never
 * rendered to the user. The container stays `dir="ltr"` (Leaflet's own pixel math assumes LTR;
 * the stylis RTL plugin would otherwise mirror its internally-managed absolute positioning), the
 * same hazard `AddressMapPicker`'s grid stand-in documents for its own marker.
 * @component NeshanMap
 */
const NeshanMap: FunctionComponent<NeshanMapProps> = ({
  value,
  onChange,
  center,
  neshanKey,
  searchPlaceholder,
  locateMeLabel,
  pinPreviewPrefix,
  resolvingLabel,
  error = false,
  errorText,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<L.Map | null>(null);
  const markerRef = useRef<L.Marker | null>(null);

  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState<NeshanSearchResult[]>([]);
  const [searching, setSearching] = useState(false);
  const [locating, setLocating] = useState(false);
  const [pinAddress, setPinAddress] = useState<string | null>(null);
  const [resolvingAddress, setResolvingAddress] = useState(false);

  const placePin = (point: LatLng, panTo = false) => {
    onChange(point);
    const map = mapRef.current;
    if (!map) return;
    const latlng = L.latLng(point.latitude, point.longitude);
    if (markerRef.current) markerRef.current.setLatLng(latlng);
    if (panTo) map.setView(latlng, Math.max(map.getZoom(), NESHAN_DEFAULT_ZOOM));
  };

  // Mount the map once; teardown on unmount.
  useEffect(() => {
    if (!containerRef.current || mapRef.current) return;
    const map = L.map(containerRef.current, { zoomControl: true }).setView(
      [value?.latitude ?? center.latitude, value?.longitude ?? center.longitude],
      NESHAN_DEFAULT_ZOOM,
    );
    L.tileLayer(NESHAN_TILE_URL_TEMPLATE.replace('{key}', neshanKey), { attribution: NESHAN_TILE_ATTRIBUTION, maxZoom: 19 }).addTo(map);

    const icon = L.divIcon({ html: PIN_HTML, className: '', iconSize: [32, 32], iconAnchor: [16, 32] });
    const marker = L.marker([value?.latitude ?? center.latitude, value?.longitude ?? center.longitude], {
      icon,
      draggable: true,
    }).addTo(map);
    marker.on('dragend', () => {
      const pos = marker.getLatLng();
      placePin({ latitude: pos.lat, longitude: pos.lng });
    });
    map.on('click', (e: L.LeafletMouseEvent) => placePin({ latitude: e.latlng.lat, longitude: e.latlng.lng }));

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

    return () => {
      map.remove();
      mapRef.current = null;
      markerRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps -- the map/marker are created exactly once; later `value` changes are applied imperatively (below), not by recreating the map.
  }, []);

  // Reverse-geocode whenever the pin settles on a new point.
  useEffect(() => {
    if (!value) return;
    let cancelled = false;
    // Deliberate setState — flips the "resolving…" label on immediately when the pin moves.
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setResolvingAddress(true);
    reverseGeocodeNeshan(value).then((address) => {
      if (!cancelled) {
        setPinAddress(address);
        setResolvingAddress(false);
      }
    });
    return () => {
      cancelled = true;
    };
  }, [value]);

  const runSearch = async () => {
    setSearching(true);
    try {
      const found = await searchNeshan(searchTerm, value ?? center);
      setResults(found);
    } finally {
      setSearching(false);
    }
  };

  const pickResult = (result: NeshanSearchResult) => {
    placePin(result.location, true);
    setResults([]);
    setSearchTerm(result.title);
  };

  const locateMe = () => {
    if (typeof navigator === 'undefined' || !navigator.geolocation) return;
    setLocating(true);
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        placePin({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }, true);
        setLocating(false);
      },
      () => setLocating(false), // denied/unavailable — advisory, the user can still tap/drag the pin
      { enableHighAccuracy: true, timeout: 10_000, maximumAge: 0 },
    );
  };

  return (
    <Stack sx={{ gap: 1 }}>
      <Stack direction="row" sx={{ gap: 1 }}>
        <TextField
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              e.preventDefault();
              runSearch();
            }
          }}
          placeholder={searchPlaceholder}
          fullWidth
          size="small"
          slotProps={{
            input: {
              endAdornment: (
                <InputAdornment position="end">
                  {searching ? (
                    <CircularProgress size={18} />
                  ) : (
                    <IconButton size="small" onClick={runSearch} aria-label={searchPlaceholder}>
                      <AppIcon icon="search" size={18} />
                    </IconButton>
                  )}
                </InputAdornment>
              ),
            },
          }}
        />
        <IconButton onClick={locateMe} aria-label={locateMeLabel} title={locateMeLabel} disabled={locating}>
          {locating ? <CircularProgress size={20} /> : <AppIcon icon="gps" size={22} color="var(--bal-primary)" />}
        </IconButton>
      </Stack>

      {results.length > 0 ? (
        <Paper elevation={0} sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 2, maxHeight: 200, overflowY: 'auto' }}>
          <List dense disablePadding>
            {results.map((result, index) => (
              <ListItemButton key={`${result.title}-${index}`} onClick={() => pickResult(result)}>
                <ListItemText primary={result.title} secondary={result.address} />
              </ListItemButton>
            ))}
          </List>
        </Paper>
      ) : null}

      <Box
        ref={containerRef}
        dir="ltr"
        sx={{
          height: 260,
          borderRadius: 2,
          border: '1px solid',
          borderColor: error ? 'var(--bal-error)' : 'divider',
          overflow: 'hidden',
        }}
      />

      {error && errorText ? (
        <Typography variant="caption" sx={{ color: 'var(--bal-error)' }}>
          {errorText}
        </Typography>
      ) : value ? (
        <Typography variant="caption" sx={{ color: 'text.secondary' }}>
          {resolvingAddress ? resolvingLabel : `${pinPreviewPrefix} ${pinAddress ?? ''}`.trim()}
        </Typography>
      ) : null}
    </Stack>
  );
};

export default NeshanMap;
