import L,{
  Map,
  TileLayer,
  Marker,
  Popup,
  polygon, DivIcon
} from 'leaflet';
import 'leaflet-routing-machine';

import {
  getLngLat,
  IMapAdapter,
  LatLng,
  MapOptions,
  Zone,
  RouteOptions,
} from './map-adapter.interface';

export class LeafletAdapter implements IMapAdapter<Marker> {
  private map!: Map;
  private markers: Record<string, Marker> = {};
  private popup?: Popup;
  private routes: Record<string, any> = {};
  private polygons: Record<number, L.Polygon> = {};

  init(container: HTMLElement, options: MapOptions): void {
    this.map = new Map(container).setView(options.center, options.zoom);

    new TileLayer(options.tileUrl, {
      attribution: '&copy; OpenStreetMap contributors',
    }).addTo(this.map);
  }

  setCenter(latLng: LatLng): void {
    this.map.setView(latLng, this.map.getZoom());
  }

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

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

    const markerOptions: L.MarkerOptions = {};
    if (options?.icon) {
      markerOptions.icon = options.icon;
    }

    if (this.markers[options.id]) {
      this.map.removeLayer(this.markers[options.id]);
    }
    const marker = new Marker(latLng, markerOptions);
    marker.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.map.removeLayer(this.markers[id]);
      delete this.markers[id];
    }
  }

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

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

  updateMarkerPopup(id: string, content: HTMLElement): void {
    const marker = this.markers[id];
    if (marker) {
      const popup = L.popup({
        offset: [-5, -30],
      }).setContent(content);
      marker.bindPopup(popup);
    }
  }

  addMarkerClickHandler(id: string, handler: (e: any) => void): void {
    const marker = this.markers[id];
    if (marker) {
      marker.on('click', handler);
    }
  }
  addZone(zones: Zone[]): void
  {
    this.updateZone(zones);
  }
  updateZone(zones: Zone[]): void
  {
    for(let zone of zones)
    {
      // Remove old polygon if it exists
      if (this.polygons[zone.id]) {
        this.map.removeLayer(this.polygons[zone.id]);
      }

      const latlngs = zone.polygon.map((geoPoint) => {
        return [geoPoint.x, geoPoint.y] as LatLng;
      });
      let color = "#ff0000"
      let opacity = 0.4;
      if(zone.color)
        color = zone.color;

      if (zone.opacity)
        opacity = zone.opacity;

      const poly = polygon(latlngs, { color, fillOpacity: opacity })
        .addTo(this.map);

      // Store polygon reference for later removal/update
      this.polygons[zone.id] = poly;
    }
  }
  openZonePopup(zone: Zone) : void
  {
    let sumLat = 0;
    let sumLng = 0;
    zone.polygon.forEach(point => {
      sumLat += point.x;
      sumLng += point.y;
    });
    const centerLat = sumLat / zone.polygon.length;
    const centerLng = sumLng / zone.polygon.length;

    if (this.popup) {
      this.map.closePopup(this.popup);
    }

    this.popup = new Popup()
      .setLatLng([centerLat, centerLng])
      .setContent(`
      <div >
        ${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>`)
      .openOn(this.map);
  }
  closePopup(): void
  {
    if (this.popup) {
      this.map.closePopup(this.popup);
      this.popup = undefined;
    }
  }

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

    const routeOptions: any = {
      waypoints: waypoints.map(wp => L.latLng(wp[0], wp[1])),
      lineOptions: {
        styles: [
          {
            color: options?.lineStyle?.color || '#3388ff',
            opacity: options?.lineStyle?.opacity ?? 1,
            weight: options?.lineStyle?.weight || 4
          }
        ],
        extendToWaypoints: false,
        addWaypoints: false,
        missingRouteTolerance: 10
      },
      createMarker: options?.showMarkers ? undefined : () => false,
      draggableWaypoints: options?.draggableWaypoints ?? false,
      routeWhileDragging: false,
      summaryTemplate: '',
      distanceTemplate: '',
      timeTemplate: '',
      show: true
    };

    if (options?.serviceUrl) {
      routeOptions.router = L.Routing.osrmv1({
        serviceUrl: options.serviceUrl
      });
    }

    const control = L.Routing
      .control(routeOptions)
      .addTo(this.map);
    this.routes[id] = control;
  }

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

  removeRoute(id: string): void {
    if (this.routes[id]) {
      this.routes[id].remove();
      delete this.routes[id];
    }
  }

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

  on(type: string, event: (e: any) => void): void
  {
    this.map.on(type, event);
  }
  destroy(): void {
    this.removeAllRoutes();
    // Clean up polygons
    Object.values(this.polygons).forEach(poly => this.map.removeLayer(poly));
    this.map.remove();
  }
}
