import React, { useState, useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import * as turf from '@turf/turf';
import apiService from './services/api';
import './styles/App.css';

const CityRow = React.memo(({ city, index, isPlaying, currentCityIndex, isSelected, onClick }) => {
  let rowClass = '';
  if (isPlaying && currentCityIndex >= 0) {
    if (index === currentCityIndex) rowClass = 'current-row';
    else if (index < currentCityIndex) rowClass = 'passed-row';
    else if (index <= currentCityIndex + 5) rowClass = 'upcoming-row';
  } else if (isSelected) {
    rowClass = 'selected-row';
  }

  return (
    <tr
      id={`city-row-${index}`}
      onClick={onClick}
      className={rowClass}
    >
      <td>{index + 1}</td>
      <td>{city.name}</td>
      <td>{(city.population / 1000).toFixed(0)}k</td>
      <td>{city.distance_km}km</td>
    </tr>
  );
});

const APP = () => {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);
  const startMarkerRef = useRef(null);
  const animationRef = useRef(null);
  const popupRef = useRef(null);
  // Refs mirror state so stale closures (e.g. style.load) always read the current value
  const mapProjectionRef = useRef('globe');
  const selectedPointRef = useRef({ lat: 51.5074, lon: -0.1278 });
  const directionRef = useRef(90);
  const lineOfSightDataRef = useRef(null);
  const flightProgressRef = useRef(0);
  const seekRef = useRef(null);
  const currentCityIndexRef = useRef(-1);

  const [selectedPoint, setSelectedPoint] = useState({ lat: 51.5074, lon: -0.1278 });
  const [direction, setDirection] = useState(90);
  const [lineOfSightData, setLineOfSightData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [isPlaying, setIsPlaying] = useState(false);
  const [flightSpeed, setFlightSpeed] = useState(1.0);
  const [mapStyle, setMapStyle] = useState('basic');
  const [mapProjection, setMapProjection] = useState('globe');
  const [tolerance, setTolerance] = useState(50);
  const [selectedCity, setSelectedCity] = useState(null);
  const [isLocked, setIsLocked] = useState(false);
  const [currentCityIndex, setCurrentCityIndex] = useState(-1);

  // Keep refs in sync with state so stale closures always have current values
  useEffect(() => { mapProjectionRef.current = mapProjection; }, [mapProjection]);
  useEffect(() => { selectedPointRef.current = selectedPoint; }, [selectedPoint]);
  useEffect(() => { directionRef.current = direction; }, [direction]);
  useEffect(() => { lineOfSightDataRef.current = lineOfSightData; }, [lineOfSightData]);

  // Auto-scroll sidebar to current city during flight
  useEffect(() => {
    if (isPlaying && currentCityIndex >= 0) {
      const row = document.getElementById(`city-row-${currentCityIndex}`);
      if (row) row.scrollIntoView({ block: 'nearest', behavior: 'auto' });
    }
  }, [currentCityIndex, isPlaying]);

  // --- Helpers ---

  // Build GeoJSON for the line, splitting at the antimeridian for flat projections
  const buildLineGeoJSON = (lineCoords) => {
    const coords = lineCoords.map(c => [c.lon, c.lat]);
    // Split segments wherever consecutive points cross the antimeridian (±180°)
    const segments = [];
    let current = [coords[0]];
    for (let i = 1; i < coords.length; i++) {
      const diff = coords[i][0] - coords[i - 1][0];
      if (Math.abs(diff) > 180) {
        segments.push(current);
        current = [coords[i]];
      } else {
        current.push(coords[i]);
      }
    }
    segments.push(current);
    if (segments.length === 1) {
      return { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: segments[0] } };
    }
    return { type: 'Feature', properties: {}, geometry: { type: 'MultiLineString', coordinates: segments } };
  };

  // Register a subtle chevron arrow image for the symbol layer
  const addArrowImage = (map) => {
    const size = 20;
    const canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext('2d');
    ctx.strokeStyle = 'rgba(255, 107, 107, 0.75)';
    ctx.lineWidth = 2.5;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    // Chevron pointing right; MapLibre rotates it to follow line direction
    ctx.moveTo(size * 0.2, size * 0.22);
    ctx.lineTo(size * 0.78, size * 0.5);
    ctx.lineTo(size * 0.2, size * 0.78);
    ctx.stroke();
    // MapLibre v5 requires ImageData, not a raw canvas element
    map.addImage('arrow-icon', ctx.getImageData(0, 0, size, size));
  };

  // --- Map initialisation (runs once) ---

  useEffect(() => {
    mapRef.current = new maplibregl.Map({
      container: mapContainerRef.current,
      style: getMapStyle(mapStyle),
      center: [-0.1278, 51.5074],
      zoom: 2,
      pitch: 0,
      projection: { type: 'globe' }
    });

    mapRef.current.on('style.load', () => {
      setupMapLayers();
    });

    return () => {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
      if (mapRef.current) mapRef.current.remove();
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const setupMapLayers = () => {
    const map = mapRef.current;
    if (!map) return;

    const isGlobe = mapProjectionRef.current === 'globe';

    // 1. Register arrow image (cleared on style reload)
    addArrowImage(map);

    // 2. Sky and terrain — globe only
    if (isGlobe) {
      map.setSky({
        'sky-color': '#199EF3',
        'sky-horizon-blend': 0.5,
        'horizon-color': '#ffffff',
        'horizon-fog-blend': 0.5,
        'fog-color': '#add8e6',
        'fog-ground-blend': 0.5
      });
      if (!map.getSource('terrain')) {
        map.addSource('terrain', {
          type: 'raster-dem',
          tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
          encoding: 'terrarium',
          tileSize: 256,
          maxzoom: 15
        });
      }
      map.setTerrain({ source: 'terrain', exaggeration: 1.5 });
    }

    // Read current values from refs to avoid stale closure problems
    const currentPoint = selectedPointRef.current;
    const currentDirection = directionRef.current;
    const currentLineData = lineOfSightDataRef.current;

    // 3. Start marker
    if (startMarkerRef.current) startMarkerRef.current.remove();
    startMarkerRef.current = new maplibregl.Marker({ color: '#FF6B6B' })
      .setLngLat([currentPoint.lon, currentPoint.lat])
      .addTo(map);

    // 4. Preview line source and layer
    if (!map.getSource('preview-line')) {
      map.addSource('preview-line', {
        type: 'geojson',
        data: { type: 'Feature', geometry: { type: 'LineString', coordinates: [] } }
      });
      map.addLayer({
        id: 'preview-line',
        type: 'line',
        source: 'preview-line',
        paint: { 'line-color': '#FF6B6B', 'line-width': 2, 'line-dasharray': [2, 2] }
      });
    }

    // 5. Cities source and layers (Symbol/Circle for high performance & terrain alignment)
    if (!map.getSource('cities')) {
      map.addSource('cities', {
        type: 'geojson',
        data: { type: 'FeatureCollection', features: [] }
      });

      // City dot (Circle)
      map.addLayer({
        id: 'cities-circle',
        type: 'circle',
        source: 'cities',
        paint: {
          'circle-radius': 6,
          'circle-color': '#e74c3c',
          'circle-stroke-width': 2,
          'circle-stroke-color': '#ffffff'
        }
      });

      // City label (Symbol)
      map.addLayer({
        id: 'cities-label',
        type: 'symbol',
        source: 'cities',
        layout: {
          'text-field': ['get', 'name'],
          'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
          'text-size': 12,
          'text-offset': [0, 1.5],
          'text-anchor': 'top',
          'text-allow-overlap': false,
          'text-ignore-placement': false
        },
        paint: {
          'text-color': '#2c3e50',
          'text-halo-color': '#ffffff',
          'text-halo-width': 2
        }
      });

      // Handle city clicks
      map.on('click', 'cities-circle', (e) => {
        const feature = e.features[0];
        if (feature) {
          const city = JSON.parse(feature.properties.cityData);
          setSelectedCity(city);
          showCityPopup(city);
        }
      });

      map.on('mouseenter', 'cities-circle', () => {
        map.getCanvas().style.cursor = 'pointer';
      });
      map.on('mouseleave', 'cities-circle', () => {
        map.getCanvas().style.cursor = '';
      });
    }

    // 6. Restore result data if it exists
    if (currentLineData) {
      renderLineOnMap(currentLineData);
      if (map.getLayer('preview-line')) {
        map.setLayoutProperty('preview-line', 'visibility', 'none');
      }
    } else {
      updatePreviewLine(currentPoint, currentDirection);
    }
  };

  // --- Projection change effect ---

  useEffect(() => {
    const map = mapRef.current;
    if (!map) return;

    const applyProjection = () => {
      if (mapProjection === 'globe') {
        map.setProjection({ type: 'globe' });
        map.easeTo({ pitch: 0, duration: 1000 });
        map.setSky({
          'sky-color': '#199EF3',
          'sky-horizon-blend': 0.5,
          'horizon-color': '#ffffff',
          'horizon-fog-blend': 0.5,
          'fog-color': '#add8e6',
          'fog-ground-blend': 0.5
        });
        if (map.getSource('terrain')) {
          map.setTerrain({ source: 'terrain', exaggeration: 1.5 });
        }
      } else if (mapProjection === 'perspective') {
        map.setProjection({ type: 'mercator' });
        map.easeTo({ pitch: 60, duration: 1000 });
        if (map.getSource('terrain')) {
          map.setTerrain({ source: 'terrain', exaggeration: 1.5 });
        }
        // Remove sky for flat perspective as it can look odd without a true globe
        map.setSky(null);
      } else {
        // Flat mode
        map.setProjection({ type: 'mercator' });
        map.easeTo({ pitch: 0, duration: 1000 });
        map.setTerrain(null);
        map.setSky(null);
      }

      // Re-render line with correct antimeridian handling for this projection
      if (lineOfSightData && map.getSource('line-of-sight')) {
        map.getSource('line-of-sight').setData(
          buildLineGeoJSON(lineOfSightData.line_coordinates)
        );
      }
    };

    if (map.isStyleLoaded()) {
      applyProjection();
    } else {
      map.once('style.load', applyProjection);
      return () => map.off('style.load', applyProjection);
    }
  }, [mapProjection]); // eslint-disable-line react-hooks/exhaustive-deps

  // --- Click handler (respects isLocked) ---

  useEffect(() => {
    if (!mapRef.current) return;

    const handleClick = (e) => {
      if (isLocked) return;
      const { lng, lat } = e.lngLat;
      // Update ref immediately (state update is async)
      selectedPointRef.current = { lat, lon: lng };
      setSelectedPoint({ lat, lon: lng });
      if (startMarkerRef.current) {
        startMarkerRef.current.setLngLat([lng, lat]);
      }
      syncCitiesToMap([]);
      const map = mapRef.current;
      if (map.getLayer('line-of-sight-arrows')) map.removeLayer('line-of-sight-arrows');
      if (map.getSource('line-of-sight')) {
        map.removeLayer('line-of-sight');
        map.removeSource('line-of-sight');
      }
      if (map.getLayer('preview-line')) {
        map.setLayoutProperty('preview-line', 'visibility', 'visible');
      }
    };

    mapRef.current.on('click', handleClick);
    return () => {
      if (mapRef.current) mapRef.current.off('click', handleClick);
    };
  }, [isLocked]);

  // Update preview line when direction or point changes
  useEffect(() => {
    if (!isLocked) {
      updatePreviewLine(selectedPoint, direction);
    }
  }, [direction, selectedPoint, isLocked]); // eslint-disable-line react-hooks/exhaustive-deps

  // --- Map style change ---

  useEffect(() => {
    if (mapRef.current) {
      mapRef.current.setStyle(getMapStyle(mapStyle));
    }
  }, [mapStyle]); // eslint-disable-line react-hooks/exhaustive-deps

  // --- Handlers ---

  const handleStartAgain = () => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      setIsPlaying(false);
      setFlightSpeed(1.0);
    }
    if (popupRef.current) {
      popupRef.current.remove();
      popupRef.current = null;
    }
    setIsLocked(false);
    setLineOfSightData(null);
    setSelectedCity(null);
    syncCitiesToMap([]);
    if (mapRef.current) {
      const map = mapRef.current;
      if (map.getLayer('line-of-sight-arrows')) map.removeLayer('line-of-sight-arrows');
      if (map.getSource('line-of-sight')) {
        map.removeLayer('line-of-sight');
        map.removeSource('line-of-sight');
      }
      if (map.getLayer('preview-line')) {
        map.setLayoutProperty('preview-line', 'visibility', 'visible');
      }
      map.flyTo({ center: [selectedPoint.lon, selectedPoint.lat], zoom: 3, pitch: 0, bearing: 0 });
    }
  };

  const syncCitiesToMap = (cities) => {
    const map = mapRef.current;
    if (!map || !map.getSource('cities')) return;

    const geojson = {
      type: 'FeatureCollection',
      features: cities.map((city) => ({
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [city.lon, city.lat] },
        properties: {
          name: city.name,
          cityData: JSON.stringify(city)
        }
      }))
    };

    map.getSource('cities').setData(geojson);
  };


  const getCountryName = (code) => {
    try {
      const regionNames = new Intl.DisplayNames(['en'], { type: 'region' });
      return regionNames.of(code.toUpperCase()) || code;
    } catch (e) {
      return code;
    }
  };

  const showCityPopup = (city) => {
    if (!mapRef.current) return;
    if (popupRef.current) popupRef.current.remove();
    const popupNode = document.createElement('div');
    popupNode.className = 'map-info-popup';
    const countryName = getCountryName(city.country);
    popupNode.innerHTML = `
      <div class="popup-header">
        <strong>${city.name}</strong>, ${countryName}
      </div>
      <div class="popup-body">
        <p>Population: ${city.population.toLocaleString()}</p>
        <p>Dist. from start: ${city.distance_km} km</p>
        <p>Deviation: ${city.off_line_km} km</p>
      </div>
    `;
    popupRef.current = new maplibregl.Popup({
      closeButton: true,
      closeOnClick: false,
      maxWidth: '300px',
      offset: [0, -20]
    })
      .setLngLat([city.lon, city.lat])
      .setDOMContent(popupNode)
      .addTo(mapRef.current);
    popupRef.current.on('close', () => setSelectedCity(null));
  };

  const startFlyOver = () => {
    if (!lineOfSightData || !mapRef.current) return;

    if (isPlaying) {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
      setIsPlaying(false);
      setFlightSpeed(1.0);
      setCurrentCityIndex(-1);
      currentCityIndexRef.current = -1;
      return;
    }

    setIsPlaying(true);
    setCurrentCityIndex(-1);
    currentCityIndexRef.current = -1;
    if (popupRef.current) popupRef.current.remove();

    const coordinates = lineOfSightData.line_coordinates.map(c => [c.lon, c.lat]);
    const route = turf.lineString(coordinates);
    const routeDistance = turf.length(route);

    let lastFetchedDist = flightProgressRef.current;
    let currentProgress = flightProgressRef.current;
    flightProgressRef.current = 0;
    let lastTimestamp = performance.now();
    let currentSpeedMultiplier = 1.0;
    let frameCount = 0;

    async function animate(currentTime) {
      if (!mapRef.current) return;

      // Apply any pending seek
      if (seekRef.current !== null) {
        currentProgress = seekRef.current;
        lastFetchedDist = currentProgress;
        lastTimestamp = currentTime;
        seekRef.current = null;
      }

      const deltaTime = currentTime - lastTimestamp;
      lastTimestamp = currentTime;

      const baseKmPerMs = 0.1;
      const pathPoints = lineOfSightDataRef.current.line_coordinates;
      const pointIndex = Math.min(
        Math.floor((currentProgress / routeDistance) * pathPoints.length),
        pathPoints.length - 1
      );
      const isOverWater = pathPoints[pointIndex]?.is_over_water;

      let futureIsOverWater = true;
      const lookAheadDistance = 2000;
      const lookAheadSteps = 10;
      for (let i = 1; i <= lookAheadSteps; i++) {
        const checkDist = currentProgress + (lookAheadDistance * i) / lookAheadSteps;
        const checkIndex = Math.min(
          Math.floor((checkDist / routeDistance) * pathPoints.length),
          pathPoints.length - 1
        );
        if (pathPoints[checkIndex] && !pathPoints[checkIndex].is_over_water) {
          futureIsOverWater = false;
          break;
        }
      }

      const targetMultiplier = (isOverWater && futureIsOverWater) ? 20.0 : 1.0;
      const acceleration = 0.005;
      if (currentSpeedMultiplier < targetMultiplier) {
        currentSpeedMultiplier = Math.min(currentSpeedMultiplier + acceleration * deltaTime, targetMultiplier);
      } else if (currentSpeedMultiplier > targetMultiplier) {
        currentSpeedMultiplier = Math.max(currentSpeedMultiplier - acceleration * deltaTime, targetMultiplier);
      }

      frameCount++;
      if (frameCount % 10 === 0) setFlightSpeed(currentSpeedMultiplier);

      currentProgress += baseKmPerMs * deltaTime * currentSpeedMultiplier;
      const phase = Math.min(currentProgress / routeDistance, 1);

      if (phase >= 1) {
        setIsPlaying(false);
        setFlightSpeed(1.0);
        setCurrentCityIndex(-1);
        currentCityIndexRef.current = -1;
        return;
      }

      const eye = turf.along(route, currentProgress).geometry.coordinates;
      const target = turf.along(route, Math.min(currentProgress + 10, routeDistance)).geometry.coordinates;
      const bearing = turf.bearing(turf.point(eye), turf.point(target));

      mapRef.current.jumpTo({ center: eye, zoom: 6, pitch: 65, bearing });

      // Find nearest city to current position and update sidebar highlight
      const cities = lineOfSightDataRef.current.conurbations;
      let nearestIdx = -1;
      let nearestDist = Infinity;
      cities.forEach((city, idx) => {
        const d = Math.abs(city.distance_km - currentProgress);
        if (d < nearestDist) { nearestDist = d; nearestIdx = idx; }
      });
      if (nearestDist < 300 && nearestIdx !== currentCityIndexRef.current) {
        currentCityIndexRef.current = nearestIdx;
        setCurrentCityIndex(nearestIdx);
      }

      if (currentProgress - lastFetchedDist > 2000) {
        lastFetchedDist = currentProgress;
        try {
          const response = await apiService.getLineOfSight(eye[1], eye[0], bearing, tolerance);
          if (response.data.success) {
            const newCities = response.data.data.conurbations;
            setLineOfSightData(prev => {
              const existingIds = new Set(prev.conurbations.map(c => c.id));
              const uniqueNewCities = newCities.filter(c => !existingIds.has(c.id));
              const adjustedCities = uniqueNewCities.map(c => ({
                ...c,
                distance_km: Math.round(c.distance_km + currentProgress)
              }));
              const updatedData = {
                ...prev,
                conurbations: [...prev.conurbations, ...adjustedCities].sort((a, b) => a.distance_km - b.distance_km)
              };
              syncCitiesToMap(updatedData.conurbations);
              return updatedData;
            });
          }
        } catch (e) {
          console.error('Error fetching more cities during flight:', e);
        }
      }

      animationRef.current = requestAnimationFrame(animate);
    }

    animationRef.current = requestAnimationFrame(animate);
  };

  const renderLineOnMap = (data) => {
    const map = mapRef.current;
    if (!map) return;

    // Remove existing layers/source (arrow layer must go before line layer)
    if (map.getLayer('line-of-sight-arrows')) map.removeLayer('line-of-sight-arrows');
    if (map.getSource('line-of-sight')) {
      map.removeLayer('line-of-sight');
      map.removeSource('line-of-sight');
    }

    const projection = mapProjectionRef.current;

    syncCitiesToMap([]);

    map.addSource('line-of-sight', {
      type: 'geojson',
      data: buildLineGeoJSON(data.line_coordinates)
    });

    map.addLayer({
      id: 'line-of-sight',
      type: 'line',
      source: 'line-of-sight',
      layout: { 'line-join': 'round', 'line-cap': 'round' },
      paint: { 'line-color': '#FF6B6B', 'line-width': 4, 'line-opacity': 0.8 }
    });

    // Subtle periodic directional arrows along the line
    map.addLayer({
      id: 'line-of-sight-arrows',
      type: 'symbol',
      source: 'line-of-sight',
      layout: {
        'symbol-placement': 'line',
        'symbol-spacing': 300,
        'icon-image': 'arrow-icon',
        'icon-size': 1.0,
        'icon-allow-overlap': true,
        'icon-ignore-placement': true
      }
    });
    addArrowImage(map);
    syncCitiesToMap(data.conurbations);

    // Zoom to show the full path from the start point
    map.flyTo({
      center: [data.line_coordinates[0].lon, data.line_coordinates[0].lat],
      zoom: 2,
      pitch: 0,
      bearing: 0
    });
  };

  const updatePreviewLine = (point, brng) => {
    const map = mapRef.current;
    if (!map || !map.getSource('preview-line')) return;

    const path = [];
    const steps = 160;
    const totalDistance = 40074; // Full Earth circumference (km)

    for (let i = 0; i <= steps; i++) {
      const dist = (totalDistance * i) / steps;
      path.push(calculateDestination(point.lat, point.lon, brng, dist));
    }

    map.getSource('preview-line').setData(
      buildLineGeoJSON(path)
    );
  };

  const calculateDestination = (lat, lon, bearing, distance) => {
    const R = 6371;
    const brng = (bearing * Math.PI) / 180;
    const φ1 = (lat * Math.PI) / 180;
    const λ1 = (lon * Math.PI) / 180;
    const δ = distance / R;

    const φ2 = Math.asin(
      Math.sin(φ1) * Math.cos(δ) +
        Math.cos(φ1) * Math.sin(δ) * Math.cos(brng)
    );
    const λ2 =
      λ1 +
      Math.atan2(
        Math.sin(brng) * Math.sin(δ) * Math.cos(φ1),
        Math.cos(δ) - Math.sin(φ1) * Math.sin(φ2)
      );

    return {
      lat: (φ2 * 180) / Math.PI,
      lon: (((λ2 * 180) / Math.PI + 540) % 360) - 180
    };
  };

  const getMapStyle = (style) => {
    if (style === 'satellite') {
      return {
        version: 8,
        sources: {
          'esri-satellite': {
            type: 'raster',
            tiles: ['https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'],
            tileSize: 256,
            attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
          }
        },
        layers: [{
          id: 'satellite-layer',
          type: 'raster',
          source: 'esri-satellite',
          minzoom: 0,
          maxzoom: 20
        }]
      };
    }
    if (style === 'map') {
      return 'https://tiles.openfreemap.org/styles/liberty';
    }
    // 'basic' — clean MapLibre demo tiles, no labels
    return 'https://demotiles.maplibre.org/style.json';
  };

  const handleLocateMe = () => {
    if (!navigator.geolocation) return;
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        const { latitude: lat, longitude: lon } = pos.coords;
        selectedPointRef.current = { lat, lon };
        setSelectedPoint({ lat, lon });
        if (startMarkerRef.current) {
          startMarkerRef.current.setLngLat([lon, lat]);
        }
        mapRef.current?.flyTo({ center: [lon, lat], zoom: 5 });
      },
      (err) => console.error('Geolocation error:', err)
    );
  };

  const handleShowLineOfSight = async () => {
    setLoading(true);
    setSelectedCity(null);
    try {
      const response = await apiService.getLineOfSight(
        selectedPoint.lat,
        selectedPoint.lon,
        direction,
        tolerance
      );
      setLineOfSightData(response.data.data);
      setIsLocked(true);
      if (mapRef.current && mapRef.current.getLayer('preview-line')) {
        mapRef.current.setLayoutProperty('preview-line', 'visibility', 'none');
      }
      renderLineOnMap(response.data.data);
    } catch (error) {
      console.error('Error fetching line of sight:', error);
    } finally {
      setLoading(false);
    }
  };

  const PROJECTIONS = [
    { id: 'globe', label: 'Globe' },
    { id: 'perspective', label: 'Perspective' },
    { id: 'flat', label: 'Flat' }
  ];

  return (
    <div className="app-container">
      <div className="map-container" ref={mapContainerRef}>
        {!isLocked && (
          <button
            className="locate-btn"
            onClick={handleLocateMe}
            title="Use my current location"
          >
            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="3"/>
              <line x1="12" y1="2" x2="12" y2="6"/>
              <line x1="12" y1="18" x2="12" y2="22"/>
              <line x1="2" y1="12" x2="6" y2="12"/>
              <line x1="18" y1="12" x2="22" y2="12"/>
            </svg>
          </button>
        )}
      </div>

      <div className="controls">
        <div className={`control-group ${isLocked ? 'disabled-controls' : ''}`}>
          <h3>Line of Sight Settings</h3>

          <div className="setting-row">
            <label>Start Point:</label>
            <span>{selectedPoint.lat.toFixed(4)}, {selectedPoint.lon.toFixed(4)}</span>
          </div>

          <div className="setting-row">
            <label>Direction (0-360°):</label>
            <input
              type="range"
              min="0"
              max="360"
              value={direction}
              disabled={isLocked}
              onChange={(e) => setDirection(parseInt(e.target.value))}
            />
            <span>{direction}°</span>
          </div>

          <div className="setting-row">
            <label>Fuzziness Tolerance (km):</label>
            <input
              type="number"
              value={tolerance}
              disabled={isLocked}
              onChange={(e) => setTolerance(parseInt(e.target.value))}
              min="10"
              max="200"
            />
            <span>{tolerance} km</span>
          </div>

          <div className="setting-row">
            <label>Map Style:</label>
            <button className={mapStyle === 'basic' ? 'active-style' : ''} onClick={() => setMapStyle('basic')}>Basic</button>
            <button className={mapStyle === 'map' ? 'active-style' : ''} onClick={() => setMapStyle('map')}>Map</button>
            <button className={mapStyle === 'satellite' ? 'active-style' : ''} onClick={() => setMapStyle('satellite')}>Satellite</button>
          </div>

          <div className="setting-row projection-row">
            <label>Projection:</label>
            <div className="projection-buttons">
              {PROJECTIONS.map(p => (
                <button
                  key={p.id}
                  className={mapProjection === p.id ? 'active-style' : ''}
                  onClick={() => setMapProjection(p.id)}
                >
                  {p.label}
                </button>
              ))}
            </div>
          </div>

          {!isLocked ? (
            <button className="action-btn" onClick={handleShowLineOfSight} disabled={loading}>
              {loading ? 'Calculating...' : '📡 Show Line of Sight'}
            </button>
          ) : (
            <>
              <button
                className="action-btn"
                onClick={startFlyOver}
                style={{ backgroundColor: isPlaying ? '#e74c3c' : '#3498db' }}
              >
                {isPlaying ? `⏹ Stop Flight (${flightSpeed.toFixed(1)}x)` : '✈️ Fly Over Route'}
              </button>
              <button className="action-btn-secondary" onClick={handleStartAgain}>
                🔄 Start Again
              </button>
            </>
          )}
        </div>

        {lineOfSightData && (
          <div className="results-panel">
            <h3>Cities Along Route ({lineOfSightData.conurbations.length})</h3>
            {isPlaying && currentCityIndex >= 0 && (
              <div className="now-passing">
                Now passing: <strong>{lineOfSightData.conurbations[currentCityIndex]?.name}</strong>
              </div>
            )}
            <p className="hint">{isPlaying ? 'Click a city to jump to it' : 'Click a city for details'}</p>
            <table>
              <thead>
                <tr>
                  <th>#</th>
                  <th>City</th>
                  <th>Pop.</th>
                  <th>Dist.</th>
                </tr>
              </thead>
              <tbody>
                {lineOfSightData.conurbations.map((city, index) => (
                  <CityRow
                    key={city.id}
                    city={city}
                    index={index}
                    isPlaying={isPlaying}
                    currentCityIndex={currentCityIndex}
                    isSelected={selectedCity?.id === city.id}
                    onClick={() => {
                      setSelectedCity(city);
                      showCityPopup(city);
                      mapRef.current.flyTo({ center: [city.lon, city.lat], zoom: 8 });
                      if (isPlaying) {
                        seekRef.current = city.distance_km;
                      }
                    }}
                  />
                ))}
              </tbody>
            </table>
          </div>
        )}

        <div className="instructions">
          <h3>How to Use</h3>
          <ol>
            <li>Click anywhere on the map to select a starting point</li>
            <li>Adjust the direction using the slider (0-360°)</li>
            <li>Set your fuzziness tolerance (how close cities must be to the line)</li>
            <li>Click "Show Line of Sight" to visualize the path and cities</li>
          </ol>
        </div>
      </div>
    </div>
  );
};

export default APP;
