import { formatDate } from "../maps/helpers";
import { formatDistance } from "../maps/helpers";
import { formatSpeed } from "../maps/helpers";
import { minutesToDaysHoursMinutes } from "../maps/helpers";
import { haversineDistance } from "../maps/helpers";

export function calculateSpeed(point1, point2) {
  if (!point1 || !point2 || !point1[4] || !point2[4]) {
    console.warn('Invalid points for speed calculation:', { point1, point2 });
    return 0;
  }

  const distanceKm = haversineDistance(point1[0], point1[1], point2[0], point2[1]); // in kilometers
  const timeDiffSeconds = point2[4] - point1[4];

  // Handle edge cases
  if (timeDiffSeconds <= 0 || distanceKm <= 0) {
    return 0;
  }

  const speedKmh = (distanceKm / timeDiffSeconds) * 3600; // Convert to km/h

  // Cap speed at reasonable maximum (e.g., 150 km/h)
  const MAX_SPEED = 150;
  return Math.min(speedKmh, MAX_SPEED);
}

// Optimize getSpeedColor by pre-calculating color stops
export const colorStopsFallback = [
  { speed: 0, color: '#00ff00' },    // Stationary/very slow (green)
  { speed: 15, color: '#00ffff' },   // Walking/jogging (cyan)
  { speed: 30, color: '#ff00ff' },   // Cycling/slow driving (magenta)
  { speed: 50, color: '#ffff00' },   // Urban driving (yellow)
  { speed: 100, color: '#ff3300' }   // Highway driving (red)
];

export function colorFormatEncode(arr) {
  return arr.map(item => `${item.speed}:${item.color}`).join('|');
}

export function colorFormatDecode(str) {
  return str.split('|').map(segment => {
    const [speed, color] = segment.split(':');
    return { speed: Number(speed), color };
  });
}

export function getSpeedColor(speedKmh, useSpeedColors, speedColorScale) {
  if (!useSpeedColors) {
    return '#0000ff';
  }

  let colorStops;

  try {
    colorStops = colorFormatDecode(speedColorScale).map(stop => ({
      ...stop,
      rgb: hexToRGB(stop.color)
    }));;
  } catch (error) { // If user has given invalid values
    colorStops = colorStopsFallback.map(stop => ({
      ...stop,
      rgb: hexToRGB(stop.color)
    }));;
  }

  // Find the appropriate color segment
  for (let i = 1; i < colorStops.length; i++) {
    if (speedKmh <= colorStops[i].speed) {
      const ratio = (speedKmh - colorStops[i-1].speed) / (colorStops[i].speed - colorStops[i-1].speed);
      const color1 = colorStops[i-1].rgb;
      const color2 = colorStops[i].rgb;

      const r = Math.round(color1.r + (color2.r - color1.r) * ratio);
      const g = Math.round(color1.g + (color2.g - color1.g) * ratio);
      const b = Math.round(color1.b + (color2.b - color1.b) * ratio);

      return `rgb(${r}, ${g}, ${b})`;
    }
  }

  return colorStops[colorStops.length - 1].color;
}

// Helper function to convert hex to RGB
function hexToRGB(hex) {
  const r = parseInt(hex.slice(1, 3), 16);
  const g = parseInt(hex.slice(3, 5), 16);
  const b = parseInt(hex.slice(5, 7), 16);
  return { r, g, b };
}

// Add new function for batch processing
function processInBatches(items, batchSize, processFn) {
  let index = 0;
  const totalItems = items.length;

  function processNextBatch() {
    const batchStartTime = performance.now();
    let processedInThisFrame = 0;

    // Process as many items as possible within our time budget
    while (index < totalItems && processedInThisFrame < 500) {
      const end = Math.min(index + batchSize, totalItems);

      // Ensure we're within bounds
      for (let i = index; i < end; i++) {
        if (items[i]) {  // Add null check
          processFn(items[i]);
        }
      }

      processedInThisFrame += (end - index);
      index = end;

      if (performance.now() - batchStartTime > 32) {
        break;
      }
    }

    if (index < totalItems) {
      setTimeout(processNextBatch, 0);
    } else {
      // Only clear the array after all processing is complete
      items.length = 0;
    }
  }

  processNextBatch();
}

export function addHighlightOnHover(polylineGroup, map, polylineCoordinates, userSettings, distanceUnit) {
  const startPoint = polylineCoordinates[0];
  const endPoint = polylineCoordinates[polylineCoordinates.length - 1];

  const firstTimestamp = formatDate(startPoint[4], userSettings.timezone);
  const lastTimestamp = formatDate(endPoint[4], userSettings.timezone);

  const minutes = Math.round((endPoint[4] - startPoint[4]) / 60);
  const timeOnRoute = minutesToDaysHoursMinutes(minutes);

  const totalDistance = polylineCoordinates.reduce((acc, curr, index, arr) => {
    if (index === 0) return acc;
    const dist = haversineDistance(arr[index - 1][0], arr[index - 1][1], curr[0], curr[1]);
    return acc + dist;
  }, 0);

  const startIcon = L.divIcon({ html: "🚥", className: "emoji-icon" });
  const finishIcon = L.divIcon({ html: "🏁", className: "emoji-icon" });

  const startMarker = L.marker([startPoint[0], startPoint[1]], { icon: startIcon });
  const endMarker = L.marker([endPoint[0], endPoint[1]], { icon: finishIcon });

  let hoverPopup = null;
  let clickedLayer = null;

  // Add events to both group and individual polylines
  polylineGroup.eachLayer((layer) => {
    if (layer instanceof L.Polyline) {
      layer.on("mouseover", function (e) {
        handleMouseOver(e);
      });

      layer.on("mouseout", function (e) {
        handleMouseOut(e);
      });

      layer.on("click", function (e) {
        handleClick(e);
      });
    }
  });

  function handleMouseOver(e) {
    // Handle both direct layer events and group propagated events
    const layer = e.layer || e.target;
    let speed = 0;

    if (layer instanceof L.Polyline) {
        // Get the coordinates array from the layer
        const coords = layer.getLatLngs();
        if (coords && coords.length >= 2) {
            const startPoint = coords[0];
            const endPoint = coords[coords.length - 1];

            // Find the corresponding markers for these coordinates
            const startMarkerData = polylineCoordinates.find(m =>
                m[0] === startPoint.lat && m[1] === startPoint.lng
            );
            const endMarkerData = polylineCoordinates.find(m =>
                m[0] === endPoint.lat && m[1] === endPoint.lng
            );

            // Calculate speed if we have both markers
            if (startMarkerData && endMarkerData) {
                speed = startMarkerData[5] || endMarkerData[5] || 0;
            }
        }
    }

    // Don't apply hover styles if this is the clicked layer
    if (!clickedLayer) {
        // Apply style to all segments in the group
        polylineGroup.eachLayer((segment) => {
            if (segment instanceof L.Polyline) {
                const newStyle = {
                    weight: 8,
                    opacity: 1
                };

                // Only change color if speed-colored routes are not enabled
                if (!userSettings.speed_colored_routes) {
                    newStyle.color = 'yellow';  // Highlight color
                }

                segment.setStyle(newStyle);
            }
        });

        startMarker.addTo(map);
        endMarker.addTo(map);

        const popupContent = `
            <strong>Start:</strong> ${firstTimestamp}<br>
            <strong>End:</strong> ${lastTimestamp}<br>
            <strong>Duration:</strong> ${timeOnRoute}<br>
            <strong>Total Distance:</strong> ${formatDistance(totalDistance, distanceUnit)}<br>
            <strong>Current Speed:</strong> ${formatSpeed(speed, distanceUnit)}
        `;

        if (hoverPopup) {
            map.closePopup(hoverPopup);
        }

        hoverPopup = L.popup()
            .setLatLng(e.latlng)
            .setContent(popupContent)
            .addTo(map);
    }
  }

  function handleMouseOut(e) {
    // If there's a clicked state, maintain it
    if (clickedLayer && polylineGroup.clickedState) {
        polylineGroup.eachLayer((layer) => {
            if (layer instanceof L.Polyline) {
                if (layer === clickedLayer || layer.options.originalPath === clickedLayer.options.originalPath) {
                    layer.setStyle(polylineGroup.clickedState.style);
                }
            }
        });
        return;
    }

    // Apply normal style only if there's no clicked layer
    polylineGroup.eachLayer((layer) => {
        if (layer instanceof L.Polyline) {
            const originalStyle = {
                weight: 3,
                opacity: userSettings.route_opacity,
                color: layer.options.originalColor
            };
            layer.setStyle(originalStyle);
        }
    });

    if (hoverPopup && !clickedLayer) {
        map.closePopup(hoverPopup);
        map.removeLayer(startMarker);
        map.removeLayer(endMarker);
    }
  }

  function handleClick(e) {
    const newClickedLayer = e.target;

    // If clicking the same route that's already clicked, do nothing
    if (clickedLayer === newClickedLayer) {
        return;
    }

    // Store reference to previous clicked layer before updating
    const previousClickedLayer = clickedLayer;

    // Update clicked layer reference
    clickedLayer = newClickedLayer;

    // Reset previous clicked layer if it exists
    if (previousClickedLayer) {
      previousClickedLayer.setStyle({
          weight: 3,
          opacity: userSettings.route_opacity,
          color: previousClickedLayer.options.originalColor
      });
    }

    // Define style for clicked state
    const clickedStyle = {
      weight: 8,
      opacity: 1,
      color: userSettings.speed_colored_routes ? clickedLayer.options.originalColor : 'yellow'
    };

    // Apply style to new clicked layer
    clickedLayer.setStyle(clickedStyle);
    clickedLayer.bringToFront();

    // Update clicked state
    polylineGroup.clickedState = {
      layer: clickedLayer,
      style: clickedStyle
    };

    startMarker.addTo(map);
    endMarker.addTo(map);

    const popupContent = `
      <strong>Start:</strong> ${firstTimestamp}<br>
      <strong>End:</strong> ${lastTimestamp}<br>
      <strong>Duration:</strong> ${timeOnRoute}<br>
      <strong>Total Distance:</strong> ${formatDistance(totalDistance, distanceUnit)}<br>
      <strong>Current Speed:</strong> ${formatSpeed(clickedLayer.options.speed || 0, distanceUnit)}
    `;

    if (hoverPopup) {
      map.closePopup(hoverPopup);
    }

    hoverPopup = L.popup()
        .setLatLng(e.latlng)
        .setContent(popupContent)
        .addTo(map);

    // Prevent the click event from propagating to the map
    L.DomEvent.stopPropagation(e);
  }

  // Reset highlight when clicking elsewhere on the map
  map.on('click', function () {
    if (clickedLayer) {
        const clickedGroup = clickedLayer.polylineGroup || polylineGroup;
        clickedGroup.eachLayer((layer) => {
            if (layer instanceof L.Polyline) {
                layer.setStyle({
                    weight: 3,
                    opacity: userSettings.route_opacity,
                    color: layer.options.originalColor
                });
            }
        });
        clickedLayer = null;
        clickedGroup.clickedState = null;
    }
    if (hoverPopup) {
        map.closePopup(hoverPopup);
        map.removeLayer(startMarker);
        map.removeLayer(endMarker);
    }
  });

  // Keep the original group events as a fallback
  polylineGroup.on("mouseover", handleMouseOver);
  polylineGroup.on("mouseout", handleMouseOut);
  polylineGroup.on("click", handleClick);
}

export function createPolylinesLayer(markers, map, timezone, routeOpacity, userSettings, distanceUnit) {
  // Create a custom pane for our polylines with higher z-index
  if (!map.getPane('polylinesPane')) {
    map.createPane('polylinesPane');
    map.getPane('polylinesPane').style.zIndex = 450; // Above the default overlay pane (400)
  }

  const renderer = L.canvas({
    padding: 0.5,
    pane: 'polylinesPane'
  });

  const splitPolylines = [];
  let currentPolyline = [];
  const distanceThresholdMeters = parseInt(userSettings.meters_between_routes) || 500;
  const timeThresholdMinutes = parseInt(userSettings.minutes_between_routes) || 60;

  for (let i = 0, len = markers.length; i < len; i++) {
    if (currentPolyline.length === 0) {
      currentPolyline.push(markers[i]);
    } else {
      const lastPoint = currentPolyline[currentPolyline.length - 1];
      const currentPoint = markers[i];
      const distance = haversineDistance(lastPoint[0], lastPoint[1], currentPoint[0], currentPoint[1]);
      const timeDifference = (currentPoint[4] - lastPoint[4]) / 60;

      if (distance > distanceThresholdMeters || timeDifference > timeThresholdMinutes) {
        splitPolylines.push([...currentPolyline]);
        currentPolyline = [currentPoint];
      } else {
        currentPolyline.push(currentPoint);
      }
    }
  }

  if (currentPolyline.length > 0) {
    splitPolylines.push(currentPolyline);
  }

  // Create the layer group with the polylines
  const layerGroup = L.layerGroup(
    splitPolylines.map((polylineCoordinates, groupIndex) => {
      const segmentGroup = L.featureGroup();
      const segments = [];

      for (let i = 0; i < polylineCoordinates.length - 1; i++) {
        const speed = calculateSpeed(polylineCoordinates[i], polylineCoordinates[i + 1]);
        const color = getSpeedColor(speed, userSettings.speed_colored_routes, userSettings.speed_color_scale);

        const segment = L.polyline(
          [
            [polylineCoordinates[i][0], polylineCoordinates[i][1]],
            [polylineCoordinates[i + 1][0], polylineCoordinates[i + 1][1]]
          ],
          {
            renderer: renderer,
            color: color,
            originalColor: color,
            opacity: routeOpacity,
            weight: 3,
            speed: speed,
            interactive: true,
            pane: 'polylinesPane',
            bubblingMouseEvents: false
          }
        );

        segments.push(segment);
        segmentGroup.addLayer(segment);
      }

      // Add mouseover/mouseout to the entire group
      segmentGroup.on('mouseover', function(e) {
        L.DomEvent.stopPropagation(e);
        segments.forEach(segment => {
          segment.setStyle({
            weight: 8,
            opacity: 1
          });
          if (map.hasLayer(segment)) {
            segment.bringToFront();
          }
        });
      });

      segmentGroup.on('mouseout', function(e) {
        L.DomEvent.stopPropagation(e);
        segments.forEach(segment => {
          segment.setStyle({
            weight: 3,
            opacity: routeOpacity,
            color: segment.options.originalColor
          });
        });
      });

      // Make the group interactive
      segmentGroup.options.interactive = true;
      segmentGroup.options.bubblingMouseEvents = false;

      // Store the original coordinates for later use
      segmentGroup._polylineCoordinates = polylineCoordinates;

      // Add the hover functionality to the group
      addHighlightOnHover(segmentGroup, map, polylineCoordinates, userSettings, distanceUnit);

      return segmentGroup;
    })
  );

  // Add CSS to ensure our pane receives mouse events
  const style = document.createElement('style');
  style.textContent = `
    .leaflet-polylinesPane-pane {
      pointer-events: auto !important;
    }
    .leaflet-polylinesPane-pane canvas {
      pointer-events: auto !important;
    }
  `;
  document.head.appendChild(style);

  // Add to map and return
  layerGroup.addTo(map);

  return layerGroup;
}

export function updatePolylinesColors(polylinesLayer, useSpeedColors, speedColorScale) {
  const defaultStyle = {
    color: '#0000ff',
    originalColor: '#0000ff'
  };

  // More efficient segment collection
  const segments = new Array();
  polylinesLayer.eachLayer(groupLayer => {
    if (groupLayer instanceof L.LayerGroup) {
      groupLayer.eachLayer(segment => {
        if (segment instanceof L.Polyline) {
          segments.push(segment);
        }
      });
    }
  });

  // Reuse style object to reduce garbage collection
  const styleObj = {};

  // Process segments in larger batches
  processInBatches(segments, 200, (segment) => {
    try {
      if (!useSpeedColors) {
        segment.setStyle(defaultStyle);
        return;
      }

      const speed = segment.options.speed || 0;
      const newColor = getSpeedColor(speed, true, speedColorScale);

      // Reuse style object
      styleObj.color = newColor;
      styleObj.originalColor = newColor;
      segment.setStyle(styleObj);
    } catch (error) {
      console.error('Error processing segment:', error);
    }
  });
}

export function updatePolylinesOpacity(polylinesLayer, opacity) {
  const segments = [];

  // Collect all segments first
  polylinesLayer.eachLayer((groupLayer) => {
    if (groupLayer instanceof L.LayerGroup) {
      groupLayer.eachLayer((segment) => {
        if (segment instanceof L.Polyline) {
          segments.push(segment);
        }
      });
    }
  });

  // Process segments in batches of 50
  processInBatches(segments, 50, (segment) => {
    segment.setStyle({ opacity: opacity });
  });
}

export function reestablishPolylineEventHandlers(polylinesLayer, map, userSettings, distanceUnit) {
  let groupsProcessed = 0;
  let segmentsProcessed = 0;

  // Re-establish event handlers for all polyline groups
  polylinesLayer.eachLayer((groupLayer) => {
    if (groupLayer instanceof L.LayerGroup || groupLayer instanceof L.FeatureGroup) {
      groupsProcessed++;

      let segments = [];

      groupLayer.eachLayer((segment) => {
        if (segment instanceof L.Polyline) {
          segments.push(segment);
          segmentsProcessed++;
        }
      });

      // If we have stored polyline coordinates, use them; otherwise create a basic representation
      let polylineCoordinates = groupLayer._polylineCoordinates || [];

      if (polylineCoordinates.length === 0) {
        // Fallback: reconstruct coordinates from segments
        const coordsMap = new Map();
        segments.forEach(segment => {
          const coords = segment.getLatLngs();
          coords.forEach(coord => {
            const key = `${coord.lat.toFixed(6)},${coord.lng.toFixed(6)}`;
            if (!coordsMap.has(key)) {
              const timestamp = segment.options.timestamp || Date.now() / 1000;
              const speed = segment.options.speed || 0;
              coordsMap.set(key, [coord.lat, coord.lng, 0, 0, timestamp, speed]);
            }
          });
        });
        polylineCoordinates = Array.from(coordsMap.values());
      }

      // Re-establish the highlight hover functionality
      if (polylineCoordinates.length > 0) {
        addHighlightOnHover(groupLayer, map, polylineCoordinates, userSettings, distanceUnit);
      }

      // Re-establish basic group event handlers
      groupLayer.on('mouseover', function(e) {
        L.DomEvent.stopPropagation(e);
        segments.forEach(segment => {
          segment.setStyle({
            weight: 8,
            opacity: 1
          });
          if (map.hasLayer(segment)) {
            segment.bringToFront();
          }
        });
      });

      groupLayer.on('mouseout', function(e) {
        L.DomEvent.stopPropagation(e);
        segments.forEach(segment => {
          segment.setStyle({
            weight: 3,
            opacity: userSettings.route_opacity,
            color: segment.options.originalColor
          });
        });
      });

      groupLayer.on('click', function(e) {
        // Click handler placeholder
      });

      // Ensure the group is interactive
      groupLayer.options.interactive = true;
      groupLayer.options.bubblingMouseEvents = false;
    }
  });
}



export function managePaneVisibility(map, activeLayerType) {
  const polylinesPane = map.getPane('polylinesPane');
  const tracksPane = map.getPane('tracksPane');

  if (activeLayerType === 'routes') {
    // Enable polylines pane events and disable tracks pane events
    if (polylinesPane) {
      polylinesPane.style.pointerEvents = 'auto';
      polylinesPane.style.zIndex = 470; // Temporarily boost above tracks
    }
    if (tracksPane) {
      tracksPane.style.pointerEvents = 'none';
    }
  } else if (activeLayerType === 'tracks') {
    // Enable tracks pane events and disable polylines pane events
    if (tracksPane) {
      tracksPane.style.pointerEvents = 'auto';
      tracksPane.style.zIndex = 470; // Boost above polylines
    }
    if (polylinesPane) {
      polylinesPane.style.pointerEvents = 'none';
      polylinesPane.style.zIndex = 450; // Reset to original
    }
  } else {
    // Both layers might be active or neither - enable both
    if (polylinesPane) {
      polylinesPane.style.pointerEvents = 'auto';
      polylinesPane.style.zIndex = 450; // Reset to original
    }
    if (tracksPane) {
      tracksPane.style.pointerEvents = 'auto';
      tracksPane.style.zIndex = 460; // Reset to original
    }
  }
}
