import L, {
  Layer,
  LayerGroup,
  Map,
  type LatLngExpression,
  latLng,
} from 'leaflet';
import React, {
  LegacyRef,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';

import { BsMap } from 'react-icons/bs';
import deflate from '../../utils/deflate';
import { AiOutlineFullscreen, AiOutlineFullscreenExit } from 'react-icons/ai';
import Title from '../title';
import './styles.css';
import { exitFullscreen, goFullscreen } from '../../utils/fullscreen';
import BaseMapButton from '../filterButton';
import LateralMenu from './lateralMenu';
import FloatingMenu from '../floatingMenu';

const MG_CENTROID = [-18.1, -44.38];
const BASE_MAP_GOOGLE =
  'https://mt1.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}';
const BASE_MAPA_ARCGIS =
  'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
const BASE_MAPA_OSM = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const BASE_MAPA_ESRI =
  'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}';
const ZOOM_LOWEST_LAYER = 18;
export default function CriacaoLocaisDeInteresse({
  initialMarkers,
  initialTipoSugerido,
  style,
  initialPolygons,
  isMapLock,
}: {
  initialMarkers?: L.LatLng[];
  initialTipoSugerido?: string;
  style?: React.CSSProperties;
  initialPolygons?: Array<any>;
  isMapLock?: boolean;
}) {
  //Childrens
  const BaseMapMenuOptions = ({
    name,
    layer,
  }: {
    name: string;
    layer: Layer;
  }) =>
    useMemo(() => {
      return (
        <button
          onClick={() => {
            removeAllBaseMapLayers();
            if (mapRef.current) {
              if (mapRef.current.getZoom() > ZOOM_LOWEST_LAYER)
                mapRef.current.setZoom(ZOOM_LOWEST_LAYER);
              layer.addTo(mapRef.current);
            }
          }}
        >
          {name}
        </button>
      );
    }, []);
  const ShowFullScreenMarker = ({ fullscreen }: { fullscreen: boolean }) =>
    useMemo(() => {
      if (fullscreen) {
        return (
          <>
            <AiOutlineFullscreenExit
              onClick={() => {
                exitFullscreen();
                setFullscreen(false);
              }}
              size={40}
              style={{
                position: 'fixed',
                color: 'red',
                zIndex: 99999999,
                top: 10,
                right: 10,
                cursor: 'pointer',
              }}
            />
          </>
        );
      } else if (!isMapLocked())
        return (
          <AiOutlineFullscreen
            size={30}
            style={{
              position: 'absolute',
              top: 10,
              right: 10,
              cursor: 'pointer',
              color: 'red',
              zIndex: 999999999999,
            }}
            onClick={() => {
              goFullscreen();
              setFullscreen(true);
            }}
          />
        );
      return <></>;
    }, [fullscreen]);
  //Properties
  const stylesFullscreen: React.CSSProperties = {
    position: 'fixed',
    width: '100vw',
    height: '100vh',
    top: 0,
    left: 0,
    zIndex: 4,
    backgroundColor: 'white',
  };

  const limiteSobreposicao = 4;
  const [fullscreen, setFullscreen] = useState(false);
  const markerLocations = useRef<L.LatLng[]>(initialMarkers || []);
  const tipoSugerido = useRef(initialTipoSugerido || '');
  const [opcao, setOpcao] = useState(tipoSugerido.current);
  const mapRefDiv = useRef<HTMLElement>();
  const mapRef = useRef<Map>();
  const baseMapsRef = useRef<Record<string, L.TileLayer>>({});
  const [tipo, setTipo] = useState<string>('');
  const [descricao, setDescricao] = useState<string>('');
  useEffect(() => {
    setTipo(tipoSugerido.current);
  }, [tipoSugerido.current]);
  const [nome, setNome] = useState<string>('');
  const layerGroupRef = useRef<L.LayerGroup>();
  const layerGroupInitialPolygonsRef = useRef<L.LayerGroup>();
  const [layerPolygons, setLayerPolygons] = useState<
    {
      name: string;
      layer: L.TileLayer | L.GeoJSON;
      color: string;
    }[]
  >([]);
  const layerPolygonsRef = useRef<(L.TileLayer | L.GeoJSON)[]>([]);
  const pastColors = useRef<Record<string, number>>({});
  const initialView: LatLngExpression = [-20.2089622, -43.4796796];
  function randomColor() {
    let color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
    while (Object.keys(pastColors.current).includes(color)) {
      color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
    }
    return color;
  }
  const toggleLayer = useCallback((layer: L.TileLayer | L.GeoJSON) => {
    console.log('inside toggle layer');
    if (layerGroupInitialPolygonsRef.current) {
      if (layerGroupInitialPolygonsRef.current.hasLayer(layer)) {
        layerGroupInitialPolygonsRef.current.removeLayer(layer);
      } else {
        layerGroupInitialPolygonsRef.current.addLayer(layer);
      }
    }
  }, []);
  const lockMap = useCallback(() => {
    mapRef.current?.dragging.disable();
    mapRef.current?.touchZoom.disable();
    mapRef.current?.doubleClickZoom.disable();
    mapRef.current?.scrollWheelZoom.disable();
    mapRef.current?.boxZoom.disable();
    mapRef.current?.keyboard.disable();
    mapRef.current?.tap?.disable();
  }, []);
  const unlockMap = useCallback(() => {
    mapRef.current?.dragging.enable();
    mapRef.current?.touchZoom.enable();
    mapRef.current?.doubleClickZoom.enable();
    mapRef.current?.scrollWheelZoom.enable();
    mapRef.current?.boxZoom.enable();
    mapRef.current?.keyboard.enable();
    mapRef.current?.tap?.enable();
  }, []);
  const isMapLocked = useCallback(() => {
    return isMapLock;
  }, [isMapLock]);
  const setMapInitialLocation = useCallback(
    (initialPos: [number, number], initialZoom = 5.6) => {
      mapRef.current?.panTo(initialPos);
      mapRef.current?.setZoom(initialZoom);
    },
    [],
  );
  const drawPolygon = useCallback((e: any) => {
    if (tipoSugerido.current === 'area') return;
    const firstPoint = markerLocations.current[0] || e.latlng;
    if (markerLocations.current.length + 1 === 1) {
      attOption('ponto');
      tipoSugerido.current = 'ponto';
      markerLocations.current.push(e.latlng);
    } else if (
      tipoSugerido.current !== 'area' &&
      markerLocations.current.length >= 3 &&
      sobrepondo(
        firstPoint,
        e.latlng,
        limiteSobreposicao * (1 / (mapRef.current as Map)?.getZoom()),
      )
    ) {
      attOption('area');
      tipoSugerido.current = 'area';
      markerLocations.current.push(L.latLng(firstPoint.lat, firstPoint.lng));
    } else if (markerLocations.current.length + 1 > 1) {
      attOption('linha');
      tipoSugerido.current = 'linha';
      markerLocations.current.push(e.latlng);
    } else tipoSugerido.current = '';

    attOptions();

    attMarkerLayer(
      markerLayers,
      lineLayers,
      markerLocations.current[markerLocations.current.length - 1],
    );
  }, []);

  function createMap(container: HTMLElement) {
    const m = L.map(container, {
      zoomControl: false,
      preferCanvas: true,
    }).setView(initialView, 14);

    const arcgis = L.tileLayer(BASE_MAPA_ARCGIS, {
      attribution: `
	        &copy;<a href="https://carto.com/attributions" target="_blank">CARTO</a>`,
      subdomains: 'abcd',
      maxZoom: 18,
    });
    const google = L.tileLayer(BASE_MAP_GOOGLE, {
      attribution: `
	        &copy;<a href="https://carto.com/attributions" target="_blank">CARTO</a>`,
      subdomains: 'abcd',
      maxZoom: 21,
    });
    const OSM = L.tileLayer(BASE_MAPA_OSM, {
      maxZoom: 21,
      attribution:
        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    });
    const esri = L.tileLayer(BASE_MAPA_ESRI, {
      maxZoom: 21,
    });

    baseMapsRef.current = {
      arcgis,
      google,
      OSM,
      esri,
    };
    google.addTo(m);
    return m;
  }
  const removeAllBaseMapLayers = useCallback(() => {
    baseMapsRef.current?.arcgis?.remove();
    baseMapsRef.current?.google?.remove();
    baseMapsRef.current?.esri?.remove();
    baseMapsRef.current?.OSM?.remove();
  }, []);
  const addBaseMapLayer = useCallback((layer: L.TileLayer) => {
    layer.addTo(mapRef.current as Map);
  }, []);
  const addPolygon = useCallback((coordinates: any, layer: L.LayerGroup) => {
    const polygon = new L.GeoJSON(coordinates);
    const color = randomColor();
    polygon.setStyle({ color, opacity: 0.9 });
    mapRef.current?.panTo(polygon.getBounds().getCenter());
    mapRef.current?.setZoom(18);
    layerPolygonsRef.current.push(polygon);
    polygon.addTo(layer as L.LayerGroup);
    return {
      layer: polygon,
      color,
    };
  }, []);
  useEffect(() => {
    const resizeObserver = new ResizeObserver(() => {
      mapRef.current?.invalidateSize();
    });
    resizeObserver.observe(mapRefDiv.current as HTMLElement);
  }, []);
  const sendData = useCallback(() => {}, []);
  function markerIcon(i: number, size = 20) {
    const html = `<div className="map-marker" style="width: ${size}px; height: ${size}px; background-color: #90f; border-radius: 50%; color: #fff; font-size: 26px; opacity: 50%;">${i}</div>`;
    return L.divIcon({
      html,
      className: 'map-marker',
    });
  }

  function createMarker(loc: L.LatLng, index: number) {
    const icon = markerIcon(index);
    const marker = L.marker(loc, { icon });
    return marker;
  }
  function cleanAllMarkers() {
    if (layerGroupRef.current !== undefined)
      layerGroupRef.current.clearLayers();
    markerLocations.current = [];
    setOpcao('');
    tipoSugerido.current = '';
    setTipo('');
  }

  function createLines() {
    return L.polyline(
      [
        markerLocations.current[markerLocations.current.length - 1],
        markerLocations.current[markerLocations.current.length - 2],
      ],
      { color: '#74f', opacity: 0.9 },
    );
  }

  let markerLayers: LayerGroup;
  let lineLayers: Layer;
  const polygonLayer = useRef<Layer | null>(null);
  function attMarkerLayer(ml: LayerGroup | null, ll: Layer, pontos: L.LatLng) {
    if (ml) {
      ml.remove();
      ml = null;
    }

    const m = createMarker(pontos, markerLocations.current.length);
    m.addTo(layerGroupRef.current as LayerGroup);
    if (markerLocations.current.length > 1) {
      ll = createLines();
      ll.addTo(layerGroupRef.current as LayerGroup);
    }
  }

  function sobrepondo(
    a: LatLngExpression,
    b: LatLngExpression,
    distancia: number,
  ): boolean {
    const A = latLng(a);
    let unit = 10000;
    const zoom = mapRef.current?.getZoom() as number;
    if (zoom > 18) unit = 10;
    else if (zoom > 14) unit = 100;
    else if (zoom > 11) unit = 1000;
    else if (zoom >= 9) unit = 10000;
    else unit = 20000;
    const dist = A.distanceTo(b) / unit;
    if (dist <= distancia) {
      b = a;
      return true;
    }
    return false;
  }

  function attOptions() {
    if (
      (tipoSugerido.current === 'area' || opcao === 'area') &&
      polygonLayer.current === null
    ) {
      polygonLayer.current = L.polygon(markerLocations.current).addTo(
        layerGroupRef.current as LayerGroup,
      );
    } else if (polygonLayer) {
      polygonLayer.current?.remove();
      polygonLayer.current = null;
    }
  }

  function attOption(s: string) {
    setOpcao(s);
  }

  const mapAction = useCallback((container: HTMLElement) => {
    if (mapRef.current !== undefined) return;
    mapRef.current = createMap(container);
    layerGroupRef.current = L.layerGroup();
    layerGroupRef.current.addTo(mapRef.current);
    mapRef.current.on('contextmenu', (e: any) => {
      let p: L.Layer | null = null;
      let l: L.Layer | null = null;
      const lenAux = markerLocations.current.length;
      if (lenAux === 1)
        layerGroupRef.current?.eachLayer((layer: any) => {
          if (lenAux !== markerLocations.current.length) return;
          if (
            layer._latlng?.lat ===
              (markerLocations.current as any)[
                markerLocations.current.length - 1
              ].lat &&
            layer._latlng?.lng ===
              (markerLocations.current as any)[
                markerLocations.current.length - 1
              ].lng
          ) {
            p = layer;
          }

          if (p !== null) {
            p.remove();
            markerLocations.current.pop();
          }
        });
      else
        layerGroupRef.current?.eachLayer((layer: any) => {
          if (
            lenAux !== markerLocations.current.length ||
            markerLocations.current.length === 0
          )
            return;

          if (
            layer._latlng?.lat ===
              (markerLocations.current as any)[
                markerLocations.current.length - 1
              ].lat &&
            layer._latlng?.lng ===
              (markerLocations.current as any)[
                markerLocations.current.length - 1
              ].lng &&
            layer._mapToAdd !== null
          ) {
            p = layer;
          }
          if (layer._latlngs !== undefined) {
            if (
              layer._latlngs[0]?.lat ===
                (markerLocations.current as any)[
                  markerLocations.current.length - 1
                ].lat &&
              layer._latlngs[0]?.lng ===
                (markerLocations.current as any)[
                  markerLocations.current.length - 1
                ].lng
            ) {
              l = layer;
            }
          }

          if (l !== null) {
            layerGroupRef?.current?.removeLayer(l);
            console.log(p);
            (p as any).remove();
            p = null;
            markerLocations.current.pop();
          }
        });
      if (markerLocations.current.length === 0) {
        attOption('');
        tipoSugerido.current = '';
        setTipo('');
      } else if (markerLocations.current.length === 1) {
        attOption('ponto');
        tipoSugerido.current = 'ponto';
        setTipo('ponto');
      } else {
        attOption('linha');
        tipoSugerido.current = 'linha';
        setTipo('linha');
      }
      if (polygonLayer.current) {
        layerGroupRef.current?.removeLayer(polygonLayer.current as Layer);
        polygonLayer.current = null;
      }
    });

    return {
      destroy: () => {
        (mapRef.current as Map).remove();
      },
    };
  }, []);

  function resizeMap() {
    if (mapRef.current as Map) {
      (mapRef.current as Map).invalidateSize();
    }
  }
  useEffect(() => {
    if (mapRef.current !== null || mapRef.current !== undefined) {
      mapAction(mapRefDiv.current as HTMLElement);
      layerGroupInitialPolygonsRef.current = L.layerGroup();
      setMapInitialLocation(MG_CENTROID as [number, number]);
      layerGroupInitialPolygonsRef.current?.addTo(mapRef.current as Map);
    }
  }, [mapAction, setMapInitialLocation]);
  useEffect(() => {
    if (initialPolygons) {
      const newPolygons: {
        layer: L.TileLayer | L.GeoJSON;
        name: string;
        color: string;
      }[] = [];
      layerGroupInitialPolygonsRef.current?.clearLayers();
      layerPolygonsRef.current = [];
      initialPolygons.forEach((p: any, i) => {
        const newPolygon = addPolygon(
          p.geometry,
          layerGroupInitialPolygonsRef.current as L.LayerGroup,
        );
        newPolygons.push({
          name: 'Polígono' + i,
          ...newPolygon,
        });
      });
      setLayerPolygons(newPolygons);
      console.warn(newPolygons.length);
      mapRef.current?.addEventListener('click', drawPolygon);
    }
  }, [initialPolygons, addPolygon]);
  useEffect(() => {
    if (mapRef.current !== null || mapRef.current !== undefined) {
      mapRef.current?.invalidateSize();
      if (isMapLocked()) lockMap();
      else unlockMap();
      document
        .getElementsByTagName('canvas')?.[0]
        ?.addEventListener('click', (e: any) => {
          e.preventDefault();
          e.stopPropagation();
          return;
        });
    }
  }, [isMapLock, lockMap, unlockMap, isMapLocked]);
  return (
    <div
      style={{
        display: 'flex',
        flex: '1 1',

        position: 'relative',
      }}
    >
      <ShowFullScreenMarker fullscreen={fullscreen} />
      <div
        style={{
          order: 2,
          display: 'flex',
          flex: '1 1',
          overflow: 'hidden',
          position: 'relative',
        }}
      >
        <div
          style={
            fullscreen
              ? stylesFullscreen
              : { position: 'relative', zIndex: 0, height: '100%' }
          }
          ref={mapRefDiv as LegacyRef<HTMLDivElement>}
          className="map"
          id="map"
        />
        {!isMapLocked() && (
          <>
            <BaseMapButton
              wrapperStyle={{
                top: '3.5rem',
                position: fullscreen ? 'fixed' : 'absolute',
              }}
              icon={<BsMap size="2.5rem" style={{ color: 'white' }} />}
              tooltipMessage="Mapas"
              isToggleMenu
            >
              <FloatingMenu
                ballonStyle={{
                  padding: '0.5rem',
                }}
                show={true}
              >
                <div
                  style={{
                    display: 'flex',
                    flexDirection: 'row',
                    columnGap: '1rem',
                    flex: 1,
                    alignItems: 'center',
                    justifyContent: 'center',
                  }}
                >
                  {Object.keys(baseMapsRef.current as object).map((key) => {
                    return (
                      <BaseMapMenuOptions
                        name={key}
                        layer={baseMapsRef.current[key]}
                        key={key}
                      />
                    );
                  })}
                </div>
              </FloatingMenu>
            </BaseMapButton>
            <div
              style={{
                position: 'absolute',
                top: '1rem',
                maxWidth: '10rem',
                right: 0,
                flexWrap: 'wrap',
                display: 'flex',
                flexDirection: 'row',
                columnGap: '1rem',
                zIndex: 999999,
                backgroundColor: 'rgba(0,0,0,0.5)',
              }}
            >
              {layerPolygons.map((l, i) => {
                return (
                  <div
                    style={{
                      display: 'flex',
                      flexDirection: 'row',
                      alignItems: 'center',
                      justifyContent: 'center',
                      columnGap: '0.5rem',
                      cursor: 'pointer',
                      padding: '0.5rem',
                      color: 'white',
                    }}
                    onClick={() => {
                      console.log('layer', l.layer);
                      toggleLayer(
                        layerPolygonsRef.current[i] as L.TileLayer | L.GeoJSON,
                      );
                    }}
                  >
                    <div
                      style={{
                        backgroundColor: l.color,
                        width: '1rem',
                        height: '1rem',
                        borderRadius: '100%',
                      }}
                    ></div>
                    {l.name}
                  </div>
                );
              })}
            </div>
            <LateralMenu
              iconsColor="white"
              style={{
                backgroundColor: 'rgba(0,0,0,0.5)',
                borderRadius: '0.5rem',
                marginLeft: '0.5rem',
                position: fullscreen ? 'fixed' : 'absolute',
                top: '20%',
                left: 0,

                zIndex: 999999,
              }}
            />
          </>
        )}
      </div>
    </div>
  );
}
