/**
 * Renders a dashed polyline around the coverage zone boundary.
 *
 * Prefers server-computed boundary if available (shapely concave_hull).
 * Falls back to client-side @turf/concave computation.
 *
 * Performance: ~20-50ms for 10k points (runs once per coverage change).
 */

import { useEffect, useRef, useMemo } from 'react';
import { useMap } from 'react-leaflet';
import L from 'leaflet';
import concave from '@turf/concave';
import { featureCollection, point } from '@turf/helpers';
import type { CoveragePoint, BoundaryPoint } from '@/types/index.ts';
import { logger } from '@/utils/logger.ts';

interface CoverageBoundaryProps {
  points: CoveragePoint[];
  visible: boolean;
  resolution: number; // meters — controls concave hull detail
  color?: string;
  weight?: number;
  boundary?: BoundaryPoint[]; // server-provided boundary (preferred)
}

export default function CoverageBoundary({
  points,
  visible,
  resolution,
  color = '#ffffff', // white — visible against red-to-blue gradient
  weight = 2,
  boundary,
}: CoverageBoundaryProps) {
  const map = useMap();
  const layerRef = useRef<L.LayerGroup | null>(null);

  // Compute boundary paths - prefer server boundary, fallback to client-side
  const boundaryPaths = useMemo(() => {
    if (!visible) return [];

    // Use server-provided boundary if available
    if (boundary && boundary.length >= 3) {
      const serverPath: L.LatLngExpression[] = boundary.map(
        (p) => [p.lat, p.lon] as L.LatLngExpression
      );
      return [serverPath];
    }

    // Fallback to client-side computation
    if (points.length === 0) return [];

    // Group points by siteId (fallback to 'all' when siteId not available from API)
    const bySite = new Map<string, CoveragePoint[]>();
    for (const p of points) {
      const key = p.siteId || 'all';
      let arr = bySite.get(key);
      if (!arr) {
        arr = [];
        bySite.set(key, arr);
      }
      arr.push(p);
    }

    const paths: L.LatLngExpression[][] = [];

    for (const sitePoints of bySite.values()) {
      const sitePaths = computeConcaveHulls(sitePoints, resolution);
      for (const path of sitePaths) {
        if (path.length >= 3) {
          paths.push(path);
        }
      }
    }

    return paths;
  }, [points, visible, resolution, boundary]);

  // Render / cleanup polylines
  useEffect(() => {
    // Remove old
    if (layerRef.current) {
      map.removeLayer(layerRef.current);
      layerRef.current = null;
    }

    if (!visible || boundaryPaths.length === 0) return;

    const group = L.layerGroup();

    for (const path of boundaryPaths) {
      L.polyline(path, {
        color,
        weight,
        dashArray: '8, 5',
        opacity: 0.7,
        fill: false,
        interactive: false,
      }).addTo(group);
    }

    group.addTo(map);
    layerRef.current = group;

    return () => {
      if (layerRef.current) {
        map.removeLayer(layerRef.current);
        layerRef.current = null;
      }
    };
  }, [map, visible, boundaryPaths, color, weight]);

  return null;
}

// ---------------------------------------------------------------------------
// Concave hull via Turf.js
// ---------------------------------------------------------------------------

/**
 * Compute concave hull boundary path(s) for a set of coverage points.
 *
 * Uses adaptive maxEdge based on point count and resolution:
 * - More points → smaller maxEdge for finer detail
 * - Larger resolution → larger maxEdge to avoid over-fitting
 *
 * Returns multiple paths if hull is a MultiPolygon (disjoint coverage areas).
 * Falls back to empty if hull computation fails (e.g., collinear points).
 */
function computeConcaveHulls(
  pts: CoveragePoint[],
  resolutionM: number
): L.LatLngExpression[][] {
  if (pts.length < 3) return [];

  // Convert to GeoJSON FeatureCollection of Points
  const features = pts.map((p) => point([p.lon, p.lat]));
  const fc = featureCollection(features);

  // Adaptive maxEdge based on point density:
  // - Base: resolution * 2 (tighter fit)
  // - For sparse grids (<100 pts): use larger edge to avoid holes
  // - For dense grids (>1000 pts): use smaller edge for detail
  let multiplier = 2.0;
  if (pts.length < 100) {
    multiplier = 4.0; // Sparse: wider tolerance
  } else if (pts.length > 1000) {
    multiplier = 1.5; // Dense: finer detail
  }
  const maxEdge = (resolutionM * multiplier) / 1000;

  try {
    const hull = concave(fc, { maxEdge, units: 'kilometers' });

    if (!hull) return [];

    // Handle both Polygon and MultiPolygon results
    if (hull.geometry.type === 'Polygon') {
      const coords = hull.geometry.coordinates[0];
      return [
        coords.map(
          ([lon, lat]: number[]) => [lat, lon] as L.LatLngExpression
        ),
      ];
    }

    if (hull.geometry.type === 'MultiPolygon') {
      // Return all polygons as separate boundary paths
      return hull.geometry.coordinates.map((poly) =>
        poly[0].map(
          ([lon, lat]: number[]) => [lat, lon] as L.LatLngExpression
        )
      );
    }

    return [];
  } catch (error) {
    logger.error('Coverage hull computation error:', error);
    return [];
  }
}
