import { useEffect, useState } from 'react';
import { useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet.heat';
import type { CoveragePoint } from '@/types/index.ts';

// Extend L with heat layer type
declare module 'leaflet' {
  function heatLayer(
    latlngs: Array<[number, number, number]>,
    options?: Record<string, unknown>
  ): L.Layer;
}

interface HeatmapProps {
  points: CoveragePoint[];
  visible: boolean;
  opacity?: number;
}

/**
 * Normalize RSRP to 0-1 intensity for heatmap.
 *
 * Range: -130 to -50 dBm
 *
 * -130 dBm → 0.0 (deep blue, no service)
 *  -90 dBm → 0.5 (green, fair)
 *  -50 dBm → 1.0 (red, very strong)
 */
function rsrpToIntensity(rsrp: number): number {
  const minRSRP = -130;
  const maxRSRP = -50;
  return Math.max(0, Math.min(1, (rsrp - minRSRP) / (maxRSRP - minRSRP)));
}

/**
 * Geographic scale-aware heatmap parameters.
 *
 * The key insight: we want each heatmap point to cover a constant
 * GEOGRAPHIC area (400m radius) regardless of zoom level. Since
 * leaflet.heat works in pixels, we convert:
 *
 *   pixelsPerKm = 2^zoom * 6.4  (at equator, simplified)
 *   radiusPixels = targetRadiusKm * pixelsPerKm
 *
 * With progressive clamps for visual quality:
 *   - Low zoom (≤9):  min 15px, max 45px (avoid giant blobs)
 *   - High zoom (≥10): min 30px, max 80px (fill gaps between grid points)
 *
 * maxIntensity is CONSTANT at 0.75 — no compensation needed because
 * the geographic overlap between adjacent points stays consistent
 * when radius tracks geographic scale.
 */
function getHeatmapParams(zoom: number) {
  const pixelsPerKm = Math.pow(2, zoom) * 6.4;
  const targetRadiusKm = 0.4; // 400 meters
  const radiusPixels = targetRadiusKm * pixelsPerKm;

  // Progressive clamps: wider range at high zoom for smooth fill
  const minRadius = zoom < 10 ? 15 : 30;
  const maxRadius = zoom < 10 ? 45 : 80;
  const radius = Math.max(minRadius, Math.min(maxRadius, radiusPixels));

  const blur = Math.round(radius * 0.6);

  return {
    radius: Math.round(radius),
    blur,
    maxIntensity: 0.75, // CONSTANT — geographic consistency means no compensation needed
    pixelsPerKm,
    radiusPixels,
  };
}

const HEATMAP_GRADIENT = {
  0.0: '#1a237e',   // Deep blue   (-130 dBm, no service)
  0.15: '#0d47a1',  // Dark blue
  0.25: '#2196f3',  // Blue        (-110 dBm, weak)
  0.35: '#00bcd4',  // Cyan
  0.45: '#00897b',  // Teal
  0.55: '#4caf50',  // Green       ( -90 dBm, fair)
  0.65: '#8bc34a',  // Light green
  0.75: '#ffeb3b',  // Yellow      ( -70 dBm, good)
  0.85: '#ff9800',  // Orange
  1.0: '#f44336',   // Red         ( -50 dBm, excellent)
};

export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps) {
  const map = useMap();
  const [mapZoom, setMapZoom] = useState(map.getZoom());

  // Track zoom changes
  useEffect(() => {
    const handleZoomEnd = () => setMapZoom(map.getZoom());
    map.on('zoomend', handleZoomEnd);
    return () => {
      map.off('zoomend', handleZoomEnd);
    };
  }, [map]);

  // Recreate heatmap layer when points, visibility, or zoom changes
  useEffect(() => {
    if (!visible || points.length === 0) return;

    const heatData: Array<[number, number, number]> = points.map((p) => [
      p.lat,
      p.lon,
      rsrpToIntensity(p.rsrp),
    ]);

    const { radius, blur, maxIntensity, pixelsPerKm, radiusPixels } =
      getHeatmapParams(mapZoom);

    // Debug: log geographic scale params (dev only)
    if (import.meta.env.DEV && heatData.length > 0) {
      const rsrpValues = points.map((p) => p.rsrp);
      console.log('🔍 Heatmap Geographic:', {
        zoom: mapZoom,
        pixelsPerKm: pixelsPerKm.toFixed(1),
        targetRadiusKm: 0.4,
        radiusPixelsRaw: radiusPixels.toFixed(1),
        radiusClamped: radius,
        blur,
        maxIntensity,
        points: points.length,
        rsrpRange: `${Math.min(...rsrpValues).toFixed(1)} to ${Math.max(...rsrpValues).toFixed(1)} dBm`,
      });
    }

    const heatLayer = L.heatLayer(heatData, {
      radius,
      blur,
      max: maxIntensity,
      maxZoom: 17,
      minOpacity: 0.3,
      gradient: HEATMAP_GRADIENT,
    });

    heatLayer.addTo(map);

    // Apply opacity to the canvas element
    const container = (heatLayer as unknown as { _canvas?: HTMLCanvasElement })._canvas;
    if (container) {
      container.style.opacity = String(opacity);
    }

    return () => {
      map.removeLayer(heatLayer);
    };
  }, [map, points, visible, mapZoom, opacity]);

  return null;
}
