import React, { useMemo, useState, useEffect } from 'react';
import { CircleMarker, Marker, Popup, useMap } from 'react-leaflet';
import { divIcon, Point, type Icon, type DivIcon } from 'leaflet';

export interface ClusterableItem {
  latitude?: number;
  longitude?: number;
}

export interface Cluster<T extends ClusterableItem> {
  position: [number, number];
  items: T[];
  count: number;
  isOffset?: boolean;
}

interface ClusteredMarkersProps<T extends ClusterableItem> {
  items: T[];
  getItemKey: (item: T) => string;
  onItemClick?: (item: T) => void;
  renderMarker?: (item: T) => React.ReactNode;
  renderClusterMarker?: (cluster: Cluster<T>) => React.ReactNode;
  getIcon?: (item: T) => Icon<any> | DivIcon | null;
  clusterRadius?: number; // pixels
  maxZoom?: number;
  showPopups?: boolean;
  renderPopupContent?: (item: T) => React.ReactNode;
  renderClusterPopupContent?: (cluster: Cluster<T>) => React.ReactNode;
}

/**
 * Convert meter offset to lat/lng offset
 */
const metersToLatLng = (offsetX: number, offsetY: number, refLat: number): { lat: number; lng: number } => {
  const earthRadius = 6378137; // meters
  const latRad = (refLat * Math.PI) / 180;
  const latOffset = (offsetY * 180) / (earthRadius * Math.PI);
  const lngOffset = (offsetX * 180) / (earthRadius * Math.PI * Math.cos(latRad));
  return { lat: latOffset, lng: lngOffset };
};

/**
 * Create clusters from items based on pixel distance at current zoom level
 * At max zoom, offset overlapping markers instead of clustering
 */
const createClusters = <T extends ClusterableItem>(
  items: T[],
  zoom: number,
  maxZoom: number = 18,
  clusterRadius: number = 40
): Cluster<T>[] => {
  // Filter items with valid positions
  const validItems = items.filter(item =>
    item.latitude !== undefined &&
    item.longitude !== undefined
  );

  if (validItems.length === 0) return [];

  const isMaxZoom = zoom >= maxZoom;
  const clusters: Cluster<T>[] = [];
  const processed = new Set<number>();

  const getPixelPosition = (lat: number, lng: number): Point => {
    const scale = Math.pow(2, zoom);
    const x = ((lng + 180) / 360) * 256 * scale;
    const y = ((1 - Math.log(Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)) / Math.PI) / 2) * 256 * scale;
    return new Point(x, y);
  };

  validItems.forEach((item, index) => {
    if (processed.has(index)) return;

    const cluster: Cluster<T> = {
      position: [item.latitude!, item.longitude!],
      items: [item],
      count: 1,
      isOffset: false
    };

    const itemPos = getPixelPosition(item.latitude!, item.longitude!);

    // Find nearby items
    for (let i = index + 1; i < validItems.length; i++) {
      if (processed.has(i)) continue;

      const otherItem = validItems[i];
      const otherPos = getPixelPosition(otherItem.latitude!, otherItem.longitude!);
      const distance = Math.sqrt(
        Math.pow(itemPos.x - otherPos.x, 2) + Math.pow(itemPos.y - otherPos.y, 2)
      );

      if (distance <= clusterRadius) {
        cluster.items.push(otherItem);
        cluster.count++;
        processed.add(i);
      }
    }

    // At max zoom, create offset positions instead of clustering
    if (isMaxZoom && cluster.count > 1) {
      const centerLat = cluster.items.reduce((sum, p) => sum + p.latitude!, 0) / cluster.count;
      const centerLng = cluster.items.reduce((sum, p) => sum + p.longitude!, 0) / cluster.count;

      // Create individual markers with circular offset pattern
      cluster.items.forEach((item, idx) => {
        const angle = (idx / cluster.count) * 2 * Math.PI;
        const offsetDistance = 20; // meters
        const offsetX = Math.cos(angle) * offsetDistance;
        const offsetY = Math.sin(angle) * offsetDistance;

        const offset = metersToLatLng(offsetX, offsetY, centerLat);

        clusters.push({
          position: [centerLat + offset.lat, centerLng + offset.lng],
          items: [item],
          count: 1,
          isOffset: true
        });
      });

      processed.add(index);
      return;
    }

    // Calculate cluster center (average position) for non-max zoom
    if (cluster.count > 1) {
      const avgLat = cluster.items.reduce((sum, p) => sum + p.latitude!, 0) / cluster.count;
      const avgLng = cluster.items.reduce((sum, p) => sum + p.longitude!, 0) / cluster.count;
      cluster.position = [avgLat, avgLng];
    }

    clusters.push(cluster);
    processed.add(index);
  });

  return clusters;
};

/**
 * Default cluster marker icon
 */
const createDefaultClusterIcon = (count: number) => {
  const size = count < 10 ? 32 : count < 100 ? 40 : 48;
  const iconHtml = `
    <div class="cluster-marker" style="
      width: ${size}px;
      height: ${size}px;
      border-radius: 50%;
      background: rgba(0, 102, 204, 0.8);
      border: 3px solid rgba(255, 255, 255, 0.9);
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      font-size: ${count < 100 ? '14px' : '12px'};
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    ">${count}</div>
  `;

  return divIcon({
    html: iconHtml,
    className: 'cluster-marker-icon',
    iconSize: [size, size],
    iconAnchor: [size / 2, size / 2],
  });
};

/**
 * Reusable clustered markers component for Leaflet maps
 */
export function ClusteredMarkers<T extends ClusterableItem>({
  items,
  getItemKey,
  onItemClick,
  renderMarker,
  renderClusterMarker,
  getIcon,
  clusterRadius = 40,
  maxZoom: propMaxZoom,
  showPopups = true,
  renderPopupContent,
  renderClusterPopupContent,
}: ClusteredMarkersProps<T>) {
  const map = useMap();
  const [zoom, setZoom] = useState(map.getZoom());
  const maxZoom = propMaxZoom ?? map.getMaxZoom() ?? 18;

  useEffect(() => {
    const handleZoomEnd = () => {
      setZoom(map.getZoom());
    };

    map.on('zoomend', handleZoomEnd);
    return () => {
      map.off('zoomend', handleZoomEnd);
    };
  }, [map]);

  const clusters = useMemo(() => {
    return createClusters(items, zoom, maxZoom, clusterRadius);
  }, [items, zoom, maxZoom, clusterRadius]);

  const handleClusterClick = (cluster: Cluster<T>) => {
    if (cluster.count === 1) {
      onItemClick?.(cluster.items[0]);
    } else if (!cluster.isOffset) {
      // Only zoom for non-offset clusters (not at max zoom)
      const minLat = Math.min(...cluster.items.map(p => p.latitude!));
      const maxLat = Math.max(...cluster.items.map(p => p.latitude!));
      const minLng = Math.min(...cluster.items.map(p => p.longitude!));
      const maxLng = Math.max(...cluster.items.map(p => p.longitude!));

      const bounds: [[number, number], [number, number]] = [
        [minLat, minLng],
        [maxLat, maxLng]
      ];

      map.fitBounds(bounds, {
        padding: [50, 50],
        maxZoom: Math.min(zoom + 3, maxZoom)
      });
    }
  };

  return (
    <>
      {clusters.map((cluster, idx) => {
        if (cluster.count > 1 && !cluster.isOffset) {
          // Render cluster marker
          if (renderClusterMarker) {
            return <React.Fragment key={`cluster-${idx}`}>{renderClusterMarker(cluster)}</React.Fragment>;
          }

          return (
            <Marker
              key={`cluster-${idx}`}
              position={cluster.position}
              icon={createDefaultClusterIcon(cluster.count)}
              eventHandlers={{
                click: () => handleClusterClick(cluster),
              }}
            >
              {showPopups && (
                <Popup>
                  {renderClusterPopupContent ? (
                    renderClusterPopupContent(cluster)
                  ) : (
                    <div>
                      <strong>{cluster.count} items</strong>
                      <div style={{ marginTop: '0.5rem', fontSize: '0.875rem', color: '#666' }}>
                        Click to zoom in
                      </div>
                    </div>
                  )}
                </Popup>
              )}
            </Marker>
          );
        } else {
          // Render individual marker
          const item = cluster.items[0];

          if (renderMarker) {
            return <React.Fragment key={getItemKey(item)}>{renderMarker(item)}</React.Fragment>;
          }

          const icon = getIcon?.(item);

          if (icon) {
            return (
              <Marker
                key={getItemKey(item)}
                position={[item.latitude!, item.longitude!]}
                icon={icon}
                eventHandlers={{
                  click: () => onItemClick?.(item),
                }}
              >
                {showPopups && renderPopupContent && (
                  <Popup>{renderPopupContent(item)}</Popup>
                )}
              </Marker>
            );
          }

          // Default fallback: blue circle
          return (
            <CircleMarker
              key={getItemKey(item)}
              center={[item.latitude!, item.longitude!]}
              radius={6}
              pathOptions={{
                color: '#0066cc',
                fillColor: '#0066cc',
                fillOpacity: 0.6,
                weight: 2,
              }}
              eventHandlers={{
                click: () => onItemClick?.(item),
              }}
            >
              {showPopups && renderPopupContent && (
                <Popup>{renderPopupContent(item)}</Popup>
              )}
            </CircleMarker>
          );
        }
      })}
    </>
  );
}

export default ClusteredMarkers;
