import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import loadData from '../lib/loadData';
import chroma from 'chroma-js';
import {
  MapContainer,
  TileLayer,
  GeoJSON as GeoJSONLayer,
  LayersControl,
} from 'react-leaflet';

import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';

export type MapProps = {
  layers: {
    data: string | GeoJSON.GeoJSON;
    name: string;
    colorScale?: {
      starting: string;
      ending: string;
    };
    tooltip?:
      | {
          propNames: string[];
        }
      | boolean;
    _id?: number;
  }[];
  title?: string;
  center?: { latitude: number | undefined; longitude: number | undefined };
  zoom?: number;
};

export function Map({
  layers = [
    {
      data: null,
      name: null,
      colorScale: { starting: 'blue', ending: 'red' },
      tooltip: true,
    },
  ],
  center = { latitude: 45, longitude: 45 },
  zoom = 2,
  title = '',
}: MapProps) {
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [layersData, setLayersData] = useState<any>([]);

  useEffect(() => {
    const loadDataPromises = layers.map(async (layer) => {
      let layerData: any;

      if (typeof layer.data === 'string') {
        //  If "data" is string, assume it's a URL
        setIsLoading(true);
        layerData = await loadData(layer.data).then((res: any) => {
          return JSON.parse(res);
        });
      } else {
        //  Else, expect raw GeoJSON
        layerData = layer.data;
      }

      if (layer.colorScale) {
        const colorScaleAr = chroma
          .scale([layer.colorScale.starting, layer.colorScale.ending])
          .mode('lch')
          .colors(layerData.features.length);

        layerData.features.forEach((feature, i) => {
          //  Only style if the feature doesn't have a color prop
          if (feature.color === undefined) {
            feature.color = colorScaleAr[i];
          }
        });
      }

      return { name: layer.name, data: layerData };
    });

    Promise.all(loadDataPromises).then((values) => {
      setLayersData(values);
      setIsLoading(false);
    });
  }, []);

  return isLoading ? (
    <div className="w-full flex items-center justify-center w-[600px] h-[300px]">
      <LoadingSpinner />
    </div>
  ) : (
    <MapContainer
      center={[center.latitude, center.longitude]}
      zoom={zoom}
      scrollWheelZoom={false}
      className="h-80 w-full"
      // @ts-ignore
      whenReady={(map: any) => {
        //  Enable zoom using scroll wheel
        map.target.scrollWheelZoom.enable();

        //  Create the title box
        var info = new L.Control() as any;

        info.onAdd = function () {
          this._div = L.DomUtil.create('div', 'info');
          this.update();
          return this._div;
        };

        info.update = function () {
          this._div.innerHTML = `<h4 style="font-weight: 600; background: #f9f9f9; padding: 5px; border-radius: 5px; color: #464646;">${title}</h4>`;
        };

        if (title) info.addTo(map.target);
      }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <LayersControl position="bottomright">
        {layers.map((layer) => {
          const data = layersData.find(
            (layerData) => layerData.name === layer.name
          )?.data;

          return (
            data && (
              <LayersControl.Overlay key={layer.name} checked name={layer.name}>
                <GeoJSONLayer
                  data={data}
                  //  @ts-ignore
                  pointToLayer={(feature, latlng) => {
                    //  This resolver an issue in which the bundled map was
                    //  not finding the images
                    const leafletBase =
                      'https://unpkg.com/leaflet@1.9.4/dist/images/';

                    const icon = new L.Icon({
                      iconUrl: leafletBase + 'marker-icon.png',
                      iconRetinaUrl: leafletBase + 'marker-icon-2x.png',
                      shadowUrl: leafletBase + 'marker-shadow.png',
                      iconSize: [25, 41],
                      iconAnchor: [12, 41],
                      popupAnchor: [1, -34],
                      tooltipAnchor: [16, -28],
                      shadowSize: [41, 41],
                    });

                    const iconMarker = L.marker(latlng, { icon });
                    return iconMarker;
                  }}
                  style={(geoJsonFeature: any) => {
                    //  Set the fill color of each feature when appliable
                    if (
                      !['Point', 'MultiPoint'].includes(geoJsonFeature.type)
                    ) {
                      return { color: geoJsonFeature?.color };
                    }
                  }}
                  eventHandlers={{
                    add: (e) => {
                      const featureGroup = e.target;
                      const tooltip = layer.tooltip;

                      featureGroup.eachLayer((featureLayer) => {
                        const feature = featureLayer.feature;
                        const geometryType = feature.geometry.type;

                        if (tooltip) {
                          const featurePropNames = Object.keys(
                            feature.properties
                          );
                          let includedFeaturePropNames;

                          if (tooltip === true) {
                            includedFeaturePropNames = featurePropNames;
                          } else {
                            includedFeaturePropNames = tooltip.propNames.filter(
                              (name) => featurePropNames.includes(name)
                            );
                          }

                          if (includedFeaturePropNames) {
                            const tooltipContent = includedFeaturePropNames
                              .map(
                                (name) =>
                                  `<b>${name}:</b> ${feature.properties[name]}`
                              )
                              .join('<br />');

                            featureLayer.bindTooltip(tooltipContent, {
                              direction: 'center',
                            });
                          }
                        }

                        featureLayer.on({
                          mouseover: (event) => {
                            if (
                              ['Polygon', 'MultiPolygon'].includes(geometryType)
                            ) {
                              event.target.setStyle({
                                fillOpacity: 0.5,
                              });
                            }
                          },
                          mouseout: (event) => {
                            if (
                              ['Polygon', 'MultiPolygon'].includes(geometryType)
                            ) {
                              event.target.setStyle({
                                fillOpacity: 0.2,
                              });
                            }
                          },
                        });
                      });
                    },
                  }}
                />
                ;
              </LayersControl.Overlay>
            )
          );
        })}
      </LayersControl>
    </MapContainer>
  );
}
