import { useEffect, useMemo } from 'react';
import L from 'leaflet';
import {
  MapContainer,
  Marker,
  Popup,
  TileLayer,
  Tooltip,
  useMap,
  useMapEvents,
} from 'react-leaflet';
import type { MapTileStyle } from './mapStyles';
import type { BBox, ParsedOsmResult } from '../search/types';

type MapViewProps = {
  results: ParsedOsmResult[];
  selectedResultId: string | null;
  tileStyle: MapTileStyle;
  showResultLabels: boolean;
  fitMapToResults: boolean;
  dimMap: boolean;
  onResultSelect: (id: string) => void;
  onViewportChange: (bbox: BBox) => void;
};

const DEFAULT_CENTER: [number, number] = [52.2297, 21.0122];
const DEFAULT_ZOOM = 13;

export function MapView({
  results,
  selectedResultId,
  tileStyle,
  showResultLabels,
  fitMapToResults,
  dimMap,
  onResultSelect,
  onViewportChange,
}: MapViewProps) {
  const markerIcon = useMemo(
    () =>
      L.divIcon({
        className: 'result-marker',
        html: '<span></span>',
        iconSize: [18, 18],
        iconAnchor: [9, 9],
      }),
    [],
  );

  const selectedMarkerIcon = useMemo(
    () =>
      L.divIcon({
        className: 'result-marker result-marker-selected',
        html: '<span></span>',
        iconSize: [24, 24],
        iconAnchor: [12, 12],
      }),
    [],
  );

  return (
    <MapContainer
      center={DEFAULT_CENTER}
      zoom={DEFAULT_ZOOM}
      minZoom={3}
      className={dimMap ? 'map-view map-view-dimmed' : 'map-view'}
      zoomControl
    >
      <TileLayer
        key={tileStyle.id}
        attribution={tileStyle.attribution}
        url={tileStyle.url}
        maxZoom={tileStyle.maxZoom}
      />
      <ViewportReporter onViewportChange={onViewportChange} />
      <FitResults results={results} enabled={fitMapToResults} />
      <SelectedResultFocus result={results.find((item) => item.id === selectedResultId) ?? null} />
      {results.map((result) => (
        <Marker
          key={result.id}
          position={[result.lat, result.lon]}
          icon={result.id === selectedResultId ? selectedMarkerIcon : markerIcon}
          eventHandlers={{
            click: () => onResultSelect(result.id),
          }}
        >
          <Popup>
            <strong>{result.name ?? 'Unnamed place'}</strong>
            <br />
            {result.type} {result.osmId}
          </Popup>
          {showResultLabels ? (
            <Tooltip permanent direction="top" offset={[0, -10]} opacity={0.94}>
              {result.name ?? `${result.type} ${result.osmId}`}
            </Tooltip>
          ) : null}
        </Marker>
      ))}
    </MapContainer>
  );
}

function ViewportReporter({
  onViewportChange,
}: {
  onViewportChange: (bbox: BBox) => void;
}) {
  const map = useMapEvents({
    moveend: () => onViewportChange(boundsToBBox(map.getBounds())),
    zoomend: () => onViewportChange(boundsToBBox(map.getBounds())),
  });

  useEffect(() => {
    onViewportChange(boundsToBBox(map.getBounds()));
  }, [map, onViewportChange]);

  return null;
}

function FitResults({
  results,
  enabled,
}: {
  results: ParsedOsmResult[];
  enabled: boolean;
}) {
  const map = useMap();

  useEffect(() => {
    if (!enabled || results.length === 0) {
      return;
    }

    const bounds = L.latLngBounds(results.map((result) => [result.lat, result.lon]));

    if (bounds.isValid()) {
      map.fitBounds(bounds.pad(0.2), {
        animate: true,
        maxZoom: 16,
      });
    }
  }, [enabled, map, results]);

  return null;
}

function SelectedResultFocus({ result }: { result: ParsedOsmResult | null }) {
  const map = useMap();

  useEffect(() => {
    if (result) {
      map.setView([result.lat, result.lon], Math.max(map.getZoom(), 15), {
        animate: true,
      });
    }
  }, [map, result]);

  return null;
}

function boundsToBBox(bounds: L.LatLngBounds): BBox {
  const southWest = bounds.getSouthWest();
  const northEast = bounds.getNorthEast();

  return {
    south: southWest.lat,
    west: southWest.lng,
    north: northEast.lat,
    east: northEast.lng,
  };
}
