/**
 * Custom geographic-scale heatmap using Leaflet GridLayer + Canvas tiles.
 *
 * Replaces leaflet.heat with a renderer that maintains true geographic scale:
 * a 400m coverage radius is always 400m on the ground, regardless of zoom.
 *
 * Features:
 *   - Tile-based rendering (256×256 canvas per tile)
 *   - Automatic tile cache invalidation on point/radius changes
 *   - Tile load progress reporting
 *   - Configurable geographic radius
 *   - Opacity control
 */

import { useEffect, useRef, useState, useCallback } from 'react';
import { useMap } from 'react-leaflet';
import L from 'leaflet';
import { HeatmapTileRenderer } from './HeatmapTileRenderer.ts';
import type { HeatmapPoint } from './HeatmapTileRenderer.ts';

interface GeographicHeatmapProps {
  points: HeatmapPoint[];
  visible: boolean;
  opacity?: number;
  radiusMeters?: number;
}

export default function GeographicHeatmap({
  points,
  visible,
  opacity = 0.7,
  radiusMeters = 400,
}: GeographicHeatmapProps) {
  const map = useMap();
  const layerRef = useRef<L.GridLayer | null>(null);
  const rendererRef = useRef<HeatmapTileRenderer>(
    new HeatmapTileRenderer(radiusMeters)
  );

  // Tile progress
  const [tileProgress, setTileProgress] = useState<{
    loaded: number;
    total: number;
  } | null>(null);

  // Stable reference to points for the tile factory closure
  const pointsRef = useRef<HeatmapPoint[]>(points);
  pointsRef.current = points;

  // Update renderer radius when prop changes
  useEffect(() => {
    rendererRef.current.setRadiusMeters(radiusMeters);
  }, [radiusMeters]);

  // Invalidate cache when points change (use length + first/last coords as fingerprint)
  useEffect(() => {
    if (points.length === 0) {
      rendererRef.current.setPointsHash('empty');
      return;
    }
    const first = points[0];
    const last = points[points.length - 1];
    const hash = `${points.length}:${first.lat.toFixed(4)},${first.lon.toFixed(4)}:${last.rsrp}`;
    rendererRef.current.setPointsHash(hash);
  }, [points]);

  // Create / destroy GridLayer
  const createLayer = useCallback(() => {
    // Remove existing
    if (layerRef.current) {
      map.removeLayer(layerRef.current);
      layerRef.current = null;
    }

    if (!visible || points.length === 0) {
      setTileProgress(null);
      return;
    }

    const renderer = rendererRef.current;
    const currentPointsRef = pointsRef;

    // Custom GridLayer with canvas tiles
    const HeatmapGridLayer = L.GridLayer.extend({
      createTile(
        this: L.GridLayer,
        coords: L.Coords,
        done: (err: Error | null, tile: HTMLElement) => void
      ) {
        const canvas = document.createElement('canvas');

        // Use requestAnimationFrame to avoid blocking the main thread
        requestAnimationFrame(() => {
          try {
            renderer.renderTile(
              canvas,
              currentPointsRef.current,
              coords.x,
              coords.y,
              coords.z
            );
          } catch (error) {
            console.error('Tile render error:', error);
            // Draw error indicator
            const ctx = canvas.getContext('2d');
            if (ctx) {
              canvas.width = 256;
              canvas.height = 256;
              ctx.fillStyle = 'rgba(255,0,0,0.05)';
              ctx.fillRect(0, 0, 256, 256);
            }
          }
          done(null, canvas);
        });

        return canvas;
      },
    });

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const layer = new (HeatmapGridLayer as any)({
      opacity,
      zIndex: 200,
      updateWhenZooming: false,
      updateWhenIdle: true,
    }) as L.GridLayer;

    // Track tile loading progress
    let total = 0;
    let loaded = 0;

    layer.on('tileloadstart', () => {
      total++;
      setTileProgress({ loaded, total });
    });

    layer.on('tileload', () => {
      loaded++;
      setTileProgress({ loaded, total });
    });

    layer.on('load', () => {
      // All tiles done — hide progress after short delay
      setTimeout(() => setTileProgress(null), 500);
      total = 0;
      loaded = 0;
    });

    layer.addTo(map);
    layerRef.current = layer;
  }, [map, visible, points, opacity, radiusMeters]); // eslint-disable-line react-hooks/exhaustive-deps

  // Main effect: create/recreate layer
  useEffect(() => {
    createLayer();
    return () => {
      if (layerRef.current) {
        map.removeLayer(layerRef.current);
        layerRef.current = null;
      }
    };
  }, [createLayer, map]);

  // Opacity changes without full recreate
  useEffect(() => {
    if (layerRef.current) {
      layerRef.current.setOpacity(opacity);
    }
  }, [opacity]);

  // Render tile progress indicator
  if (tileProgress && tileProgress.total > 0 && tileProgress.loaded < tileProgress.total) {
    return (
      <div
        style={{
          position: 'absolute',
          top: '10px',
          left: '50%',
          transform: 'translateX(-50%)',
          zIndex: 1000,
          background: 'rgba(0,0,0,0.75)',
          color: 'white',
          padding: '6px 14px',
          borderRadius: '6px',
          fontSize: '12px',
          pointerEvents: 'none',
          display: 'flex',
          alignItems: 'center',
          gap: '8px',
        }}
      >
        <span
          className="animate-spin"
          style={{
            display: 'inline-block',
            width: '12px',
            height: '12px',
            border: '2px solid rgba(255,255,255,0.3)',
            borderTopColor: 'white',
            borderRadius: '50%',
          }}
        />
        Rendering {tileProgress.loaded}/{tileProgress.total} tiles
      </div>
    );
  }

  return null;
}
