import { Map, Marker, NavigationControl, GeoJSONSource, Popup } from 'maplibre-gl';
import {getLngLat, IMapAdapter, LatLng, MapOptions, Zone, RouteOptions} from './map-adapter.interface';
import MapLibreGlDirections from "@maplibre/maplibre-gl-directions";
export class LibreAdapter implements IMapAdapter<Marker> {
  private map!: Map;
  private markers: Record<string, Marker> = {};
  private markerClickHandlers: Record<string, (e: any) => void> = {};
  private popup?: Popup;
  private directions: Record<string, MapLibreGlDirections> = {};
  init(container: HTMLElement, options: MapOptions): void {
    this.map = new Map({
      container,
      style: options.styleUrl!,
      center: getLngLat(options.center),
      zoom: options.zoom,
    });

    this.map.addControl(new NavigationControl(), 'top-right');
  }

  setCenter(latLng: LatLng): void {
    this.map.setCenter(getLngLat(latLng));
  }

  setZoom(zoom: number): void {
    this.map.setZoom(zoom);
  }

  addMarker(latLng: LatLng, options?: { id?: string; color?: string; icon?: HTMLElement }) {
    if (!options?.id) {
      console.warn('addMarker called without id, marker will not be tracked');
      return;
    }

    const coords = getLngLat(latLng);

    const markerOptions: any = {};
    if (options?.icon) {
      markerOptions.element = options.icon;
    } else if (options?.color) {
      markerOptions.color = options.color;
    } else {
      markerOptions.color = 'red';
    }

    if (this.markers[options.id]) {
      this.markers[options.id].remove();
    }

    const marker = new Marker(markerOptions)
      .setLngLat(coords)
      .addTo(this.map);
    this.markers[options.id] = marker;
  }

  getMarker(id: string): Marker | undefined {
    return this.markers[id];
  }

  getAllMarkers(): Record<string, Marker> {
    return this.markers;
  }

  removeMarker(id?: string): void {
    if (!id) {
      console.warn('removeMarker called without id');
      return;
    }

    if (this.markers[id]) {
      this.markers[id].remove();
      delete this.markers[id];
      // Clean up stored click handler
      delete this.markerClickHandlers[id];
    }
  }

  hasMarker(id: string): boolean {
    return !!this.markers[id];
  }

  updateMarkerIcon(id: string, icon: HTMLElement): void {
    const marker = this.markers[id];
    if (marker) {
      this.markers[id] = this.recreateMarker(id, marker, icon);
    }
  }

  updateMarkerPopup(id: string, content: HTMLElement): void {
    const marker = this.markers[id];
    if (marker) {
      // Create MapLibre popup with the HTML content
      const popup = new Popup({
        offset: [0, -30],
        maxWidth: '400px',
        className: 'delivery-popup'
      }).setDOMContent(content);
      marker.setPopup(popup);
    }
  }

  addMarkerClickHandler(id: string, handler: (e: any) => void): void {
    const marker = this.markers[id];
    if (marker) {
      // Store the handler so we can re-apply it when re-adding markers
      this.markerClickHandlers[id] = handler;
      marker.getElement().addEventListener('click', handler);
    }
  }
  addZone(zones: Zone[]): void
  {
    this.updateZone(zones);
  }
  updateZone(zones: Zone[]): void
  {    const features = zones.map((zone) => {
    const coords = zone.polygon.map((pt) => [pt.y, pt.x]);

    if (coords[0][0] !== coords[coords.length - 1][0] || coords[0][1] !== coords[coords.length - 1][1]) {
      coords.push(coords[0]);
    }

    return {
      type: 'Feature' as const,
      properties: {
        id: zone.id,
        name: zone.name,
        color: zone.color ? zone.color : '#ff0000',
        opacity: zone.opacity ? zone.opacity : 0.4,
      },
      geometry: {
        type: 'Polygon' as const,
        coordinates: [coords]
      },
    };
  });

    const geojson = {
      type: 'FeatureCollection' as const,
      features,
    };

    if (this.map.getSource("zones")) {
      (this.map.getSource("zones") as GeoJSONSource).setData(geojson);
    }
    else {
      this.map.addSource("zones", {
        type: "geojson",
        data: geojson,
      });

      this.map.addLayer({
        id: "zones-fill",
        type: "fill",
        source: "zones",
        paint: {
          "fill-color": ["get", "color"],
          "fill-opacity": ["get", "opacity"],
        },
      });

      this.map.addLayer({
        id: "zones-outline",
        type: "line",
        source: "zones",
        paint: {
          "line-color": ["get", "color"],
          "line-width": 2,
        },
      });
    }
  }
  openZonePopup(zone: Zone) : void
  {
    let sumLng = 0;
    let sumLat = 0;
    zone.polygon.forEach(point => {
      sumLng += point.y;
      sumLat += point.x;
    });
    const centerLng = sumLng / zone.polygon.length;
    const centerLat = sumLat / zone.polygon.length;

    this.closePopup();
    this.popup = new Popup({
      closeButton: true,
      closeOnClick: false,
    })
      .setLngLat([centerLng, centerLat])
      .setHTML(`
        <div class="delivery-zone">
          ${zone.name ? `<p><strong>${zone.name}</strong></p>` : ''}
          ${zone.shippingFee ? `<p>Shipping: ${zone.shippingFee}$</p>` : ''}
          ${zone.shippingScheduleStart ? `<p>Delivery: ${zone.shippingScheduleStart}</p>` : ''}
          ${zone.shippingScheduleEnd? `<p>Delivery: ${zone.shippingScheduleEnd}</p>` : ''}
        </div>`)
      .addTo(this.map);
  }
  closePopup(): void
  {
    if (this.popup) {
      this.popup.remove();
      this.popup = undefined;
    }
  }

  addRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
    this.removeRoute(id);

    const directionsConfig: any = {
      sourceName: `maplibre-gl-directions-${id}`, // Unique source name per route
      requestOptions: {
        overview: 'full',
      }
    };

    if (options?.serviceUrl) {
      let apiUrl = options.serviceUrl;

      apiUrl = apiUrl.replace(/\/$/, '');

      if (!apiUrl.endsWith('/route/v1')) {
        apiUrl = `${apiUrl}/route/v1`;
      }

      directionsConfig.api = apiUrl;
      directionsConfig.profile = options.profile || 'driving';
    }

    const directions = new MapLibreGlDirections(this.map, directionsConfig);

    directions.interactive = false;

    const coordinates = waypoints.map(wp => getLngLat(wp));
    directions.setWaypoints(coordinates);
    this.directions[id] = directions;

    if (options?.lineStyle) {
      directions.on('fetchroutesend', () => {
        this.applyRouteLineStyle(directionsConfig.sourceName, options.lineStyle);
      });
    }

    this.readdAllMarkers();
  }

  private recreateMarker(id: string, marker: Marker, element?: HTMLElement): Marker {
    const lngLat = marker.getLngLat();
    const markerElement = element || marker.getElement();
    const popup = marker.getPopup();

    // Remove old marker
    marker.remove();

    // Create new marker with updated element
    const newMarker = new Marker({ element: markerElement })
      .setLngLat(lngLat)
      .addTo(this.map);

    // Restore popup if exists
    if (popup) {
      newMarker.setPopup(popup);
    }

    // Restore click handler if exists
    if (this.markerClickHandlers[id]) {
      newMarker.getElement().addEventListener('click', this.markerClickHandlers[id]);
    }

    return newMarker;
  }

  private applyRouteLineStyle(sourceName: string, lineStyle?: any): void {
    if (!lineStyle) return;

    const routeLineLayer = `${sourceName}-routeline`;
    const routeLineCasingLayer = `${sourceName}-routeline-casing`;

    if (this.map.getLayer(routeLineLayer)) {
      if (lineStyle.color) {
        this.map.setPaintProperty(routeLineLayer, 'line-color', lineStyle.color);
      }
      if (lineStyle.opacity !== undefined) {
        this.map.setPaintProperty(routeLineLayer, 'line-opacity', lineStyle.opacity);
      }
      if (lineStyle.weight !== undefined) {
        this.map.setPaintProperty(routeLineLayer, 'line-width', lineStyle.weight);
      }
    }

    if (this.map.getLayer(routeLineCasingLayer)) {
      if (lineStyle.color) {
        this.map.setPaintProperty(routeLineCasingLayer, 'line-color', lineStyle.color);
      }
      if (lineStyle.opacity !== undefined) {
        this.map.setPaintProperty(routeLineCasingLayer, 'line-opacity', lineStyle.opacity * 0.6);
      }
    }
  }

  private readdAllMarkers(): void {
    // Re-add all markers to bring them to the front
    for (const [id, marker] of Object.entries(this.markers)) {
      this.markers[id] = this.recreateMarker(id, marker);
    }
  }

  updateRoute(id: string, waypoints: LatLng[], options?: RouteOptions): void {
    this.addRoute(id, waypoints, options);
  }

  removeRoute(id: string): void {
    if (this.directions[id]) {
      try {
        this.directions[id].destroy();
      } catch (e) {
        console.warn('Error clearing directions:', e);
      }
      delete this.directions[id];
    }
  }

  removeAllRoutes(): void {
    Object.keys(this.directions).forEach(id => this.removeRoute(id));
  }

  on(type: string, event: (e: any) => void): void
  {
    this.map.on(type, event);
  }
  destroy(): void {
    this.removeAllRoutes();
    this.map.remove();
  }
}
