import L, { LatLngBoundsExpression, LayerGroup, Map, Marker, TileLayer } from 'leaflet';
import 'leaflet.markercluster';
import { cloneDeep, merge, pick } from 'lodash';
import { LatLng } from 'src/models';
import { Ref, onMounted, ref } from 'vue';

type MapTool = 'polygons' | 'lines';
export interface MapOptions {
  center: LatLng;
  zoom: number;
  template: 'street' | 'satellite';
  height: string;
  minZoom: number;
  tools?: MapTool[];
}

const DEFAULT_CENTER = { lat: 51.505, lng: -0.09 };
const DEFAULT_ZOOM = 13;
const DEFAULT_OPTIONS: MapOptions = {
  center: DEFAULT_CENTER,
  zoom: DEFAULT_ZOOM,
  template: 'street',
  height: '800px',
  minZoom: 10,
};

const MAP_TEMPLATES = {
  street: {
    base: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    labels: null,
    maxNativeZoom: 20,
    maxZoom: 19,
  },
  satellite: {
    base: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
    labels: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
    maxNativeZoom: 19,
    maxZoom: 19,
  },
};

export function useMap(mapId: string = 'map', options?: Partial<MapOptions>) {
  const _options: MapOptions = merge({}, DEFAULT_OPTIONS, options);
  const map = ref<Map | null>(null);
  const center = ref<LatLng | null>(_options.center || null);
  const selectedMarker = ref<any>(null);
  const markersGroup = ref<any>(null);

  function clearSelectedMarker() {
    selectedMarker.value = null;
  }

  function addMarkersAsGroup(markers: { lat: number; lng: number; id: string | number; [fields: string]: any }[]) {
    if (!map.value || !ready.value || !markersGroup.value) return;
    const markersLayers = markers.map((marker) => createMarker(pick(marker, ['lat', 'lng']), marker));
    markersGroup.value.clearLayers();
    markersGroup.value.addLayers(markersLayers);
  }

  function addMarker(position: LatLng, options: Record<string, any> = {}): Marker | null {
    if (!map.value) return null;
    const marker = createMarker(position, options)!;
    addToMap(marker);
    return marker;
  }

  function createMarker(position: LatLng, options: Record<string, any> = {}): Marker | null {
    const icon = L.icon({
      iconUrl: getIconUrl(options.color),
      iconSize: [64, 64],
      iconAnchor: [32, 64],
      shadowUrl: 'marker-shadow.png',
      shadowSize: [64, 64],
      shadowAnchor: [20, 64],
    });

    const marker = L.marker(toLatLngArray(position), {
      ...options,
      icon: icon,
    });

    if (options.tooltip) {
      marker.bindTooltip(options.tooltip, {
        permanent: true,
        direction: 'top',
        offset: [0, -64],
      });
    }
    marker.on('click', (ev) => {
      selectedMarker.value = cloneDeep(options);
      // map.value?.setView(marker.getLatLng(), map.value.getZoom());
    });

    return marker;
  }

  function clearMap() {
    if (!map.value) return;
    map.value.eachLayer((layer) => {
      if (layer instanceof L.Marker) {
        map.value!.removeLayer(layer);
      }
    });
  }

  function addToMap(element: TileLayer | Marker) {
    if (map.value) element.addTo(map.value as Map | LayerGroup);
  }

  function refreshSize() {
    map.value?.invalidateSize();
  }

  function zoomIn() {
    if (map.value) map.value.zoomIn();
  }

  function zoomOut() {
    if (map.value) map.value.zoomOut();
  }

  function setZoom(zoomLevel: number) {
    if (map.value) map.value.setZoom(zoomLevel);
  }

  function setView(center: LatLng, zoomLevel?: number) {
    if (map.value) map.value.setView(toLatLngArray(center), zoomLevel || map.value.getZoom());
  }

  function fitBounds(bounds: LatLngBoundsExpression) {
    if (map.value) map.value.fitBounds(bounds);
  }

  const ready = ref(false);

  onMounted(() => {
    setTimeout(() => {
      map.value = L.map(mapId, {
        zoomAnimation: false,
      });
      map.value.on('load', () => {
        ready.value = true;
      });
      map.value.setView(toLatLngArray(_options.center), _options.zoom);
      map.value.attributionControl.setPrefix(false);

      const template = MAP_TEMPLATES[_options.template];
      const baseLayerOptions = pick(template, ['maxNativeZoom', 'maxZoom', 'minZoom']);
      const baseLayer = L.tileLayer(template.base, {
        ...baseLayerOptions,
      });
      const container = map.value.getContainer();
      container.style.height = _options.height;

      addToMap(baseLayer);

      if (template.labels) {
        const labelsLayer = L.tileLayer(template.labels, {
          ...baseLayerOptions,
        });
        addToMap(labelsLayer);
      }

      map.value.on('moveend', () => {
        const newCenter = map.value?.getCenter();
        if (newCenter) {
          center.value = {
            lat: newCenter.lat,
            lng: newCenter.lng,
          };
        }
      });

      markersGroup.value = L['markerClusterGroup']({
        showCoverageOnHover: false,
        zoomToBoundsOnClick: false,
        spiderfyOnMaxZoom: true,
        removeOutsideVisibleBounds: true,
        animate: false,
        maxClusterRadius: 20,
        disableClusteringAtZoom: 24,
        iconCreateFunction: clusterIconFn,
      });

      addToMap(markersGroup.value);
    }, 10);
  });

  const setCenter = (latlng: LatLng) => {
    if (map.value) {
      map.value.setView(toLatLngArray(latlng), map.value.getZoom());
    }
  };

  return {
    map: map as Ref<Map | null>,
    center,
    selectedMarker,
    ready,
    addMarker,
    addMarkersAsGroup,
    clearMap,
    refreshSize,
    zoomIn,
    zoomOut,
    setZoom,
    setView,
    fitBounds,
    setCenter,
    clearSelectedMarker,
  };
}

function toLatLngArray(latlng: LatLng): [number, number] {
  return [latlng.lat, latlng.lng];
}

const LABELED_COLORS = {
  red: '#FF0000',
  green: '#00FF00',
  blue: '#0077FF',
  yellow: '#FFFF00',
  cyan: '#00FFFF',
  magenta: '#FF00FF',
  black: '#202020',
  white: '#FFFFFF',
  grey: '#adadad',
  orange: '#FFA500',
};

function getIconUrl(color: string = DEFAULT_ICON_COLOR) {
  const _color = LABELED_COLORS[color] || color;
  const borderWidth = 2;
  const borderColor = darkenColor(_color, 0.2);
  const padding = borderWidth;
  const viewBoxSize = 24 + padding * 2;
  // TODO: solo esta el marker. Incluir otros iconos
  const svgTemplate = `
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="-${padding} -${padding} ${viewBoxSize} ${viewBoxSize}" width="64" height="64">
      <path fill="${_color}" stroke="${borderColor}" stroke-width="${borderWidth}" d="M12 0C7.31 0 3.5 3.81 3.5 8.5c0 5.94 8.5 15.5 8.5 15.5s8.5-9.56 8.5-15.5C20.5 3.81 16.69 0 12 0zm0 11.5c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z"/>
  </svg>
`;

  function darkenColor(color: string, amount: number): string {
    const hex = color.replace('#', '');
    const num = parseInt(hex, 16);

    const r = Math.max(0, (num >> 16) - Math.round(255 * amount));
    const g = Math.max(0, ((num >> 8) & 0x00ff) - Math.round(255 * amount));
    const b = Math.max(0, (num & 0x0000ff) - Math.round(255 * amount));

    return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
  }

  // Convertir SVG a una URL de datos
  const svgBase64 = btoa(svgTemplate);
  const dataUrl = `data:image/svg+xml;base64,${svgBase64}`;
  return dataUrl;
}

function clusterIconFn(cluster: any) {
  const markers = cluster.getAllChildMarkers();
  const count = markers.length;
  return L.divIcon({
    html: `<div style="background-color: ${DEFAULT_ICON_COLOR}; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 50%; color: white; font-size: 16px; font-weight: bold;">
    <span>${count}</span></div>`,
  });
}

const DEFAULT_ICON_COLOR = '#5fa5f5';
