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 APP = () => {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);
  const startMarkerRef = useRef(null);
  const cityMarkersRef = useRef([]);
  const animationRef = useRef(null);
  const popupRef = useRef(null);
  const [selectedPoint, setSelectedPoint] = useState({ lat: 51.5074, lon: -0.1278 });
  const [direction, setDirection] = useState(45);
  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('light'); // 'light' or 'dark'
  const [tolerance, setTolerance] = useState(50);
  const [selectedCity, setSelectedCity] = useState(null);
  const [isLocked, setIsLocked] = useState(false);

  useEffect(() => {
    // Initialize MapLibre map - ONLY ONCE
    mapRef.current = new maplibregl.Map({
      container: mapContainerRef.current,
      style: getMapStyle(mapStyle),
      center: [-0.1278, 51.5074], // London
      zoom: 2,
      pitch: 0,
      projection: 'globe' // Enable 3D Globe
    });

    mapRef.current.on('style.load', () => {
      console.log('Map style loaded or changed');
      setupMapLayers();
    });

    return () => {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
      if (mapRef.current) mapRef.current.remove();
    };
  }, []); // Run only once

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

    // 1. Add atmosphere/sky effects
    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
    });
    
    // 2. Add terrain source for 3D effect
    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 });

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

    // 4. Initialize 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. Restore results line and city markers if they existed
    if (lineOfSightData) {
      renderLineOnMap(lineOfSightData);
      // Hide preview if results are shown
      if (map.getLayer('preview-line')) {
        map.setLayoutProperty('preview-line', 'visibility', 'none');
      }
    } else {
      updatePreviewLine(selectedPoint, direction);
    }
  };

  // Separate effect for the click listener that respects isLocked
  useEffect(() => {
    if (!mapRef.current) return;

    const handleClick = (e) => {
      // Don't allow moving start point if locked
      if (isLocked) return;

      const { lng, lat } = e.lngLat;
      setSelectedPoint({ lat, lon: lng });
      
      if (startMarkerRef.current) {
        startMarkerRef.current.setLngLat([lng, lat]);
      }

      // Clear previous final results when moving start point
      clearCityMarkers();
      if (mapRef.current.getSource('line-of-sight')) {
        mapRef.current.removeLayer('line-of-sight');
        mapRef.current.removeSource('line-of-sight');
      }
    };

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

  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);
    clearCityMarkers();
    
    if (mapRef.current) {
      if (mapRef.current.getSource('line-of-sight')) {
        mapRef.current.removeLayer('line-of-sight');
        mapRef.current.removeSource('line-of-sight');
      }
      // Re-enable preview line if it was hidden
      if (mapRef.current.getLayer('preview-line')) {
        mapRef.current.setLayoutProperty('preview-line', 'visibility', 'visible');
      }
      
      // Reset map pitch and zoom
      mapRef.current.flyTo({
        center: [selectedPoint.lon, selectedPoint.lat],
        zoom: 3,
        pitch: 0,
        bearing: 0
      });
    }
  };

  const clearCityMarkers = () => {
    if (cityMarkersRef.current) {
      cityMarkersRef.current.forEach(marker => marker.remove());
      cityMarkersRef.current = [];
    }
  };

  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);
      return;
    }

    setIsPlaying(true);
    setSelectedCity(null);
    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);
    const startTime = performance.now();

    let lastNearestCityId = null;
    let lastFetchedDist = 0;
    let currentProgress = 0;
    let lastTimestamp = performance.now();
    let currentSpeedMultiplier = 1.0;
    let frameCount = 0;

    async function animate(currentTime) {
      if (!mapRef.current) return;
      
      const deltaTime = currentTime - lastTimestamp;
      lastTimestamp = currentTime;

      const baseKmPerMs = 0.1; // 100km per second
      
      const pathPoints = lineOfSightData.line_coordinates;
      const pointIndex = Math.min(
        Math.floor((currentProgress / routeDistance) * pathPoints.length),
        pathPoints.length - 1
      );
      const isOverWater = pathPoints[pointIndex]?.is_over_water;

      // Predictive land detection (look ahead 2000km)
      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);
        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: bearing
      });

      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)
              };
              addMarkersToMap(adjustedCities, prev.conurbations.length);
              return updatedData;
            });
          }
        } catch (e) {
          console.error('Error fetching more cities during flight:', e);
        }
      }

      const nearestCity = lineOfSightData.conurbations.find(city => 
        Math.abs(city.distance_km - currentProgress) < 100 
      );

      if (nearestCity && nearestCity.id !== lastNearestCityId) {
        lastNearestCityId = nearestCity.id;
        setSelectedCity(nearestCity);
        showCityPopup(nearestCity);
      }
      
      animationRef.current = requestAnimationFrame(animate);
    }
    
    animationRef.current = requestAnimationFrame(animate);
  };

  const addMarkersToMap = (cities, startIndex = 0) => {
    const map = mapRef.current;
    if (!map) return;

    cities.forEach((city, index) => {
      const displayIndex = startIndex + index + 1;
      
      const el = document.createElement('div');
      el.className = 'city-marker';
      el.innerHTML = `
        <div class="marker-number">${displayIndex}</div>
        <div class="marker-dot"></div>
        <div class="marker-label">${city.name}</div>
        <div class="marker-pop">${(city.population / 1000).toFixed(0)}k</div>
      `;

      const marker = new maplibregl.Marker(el)
        .setLngLat([city.lon, city.lat])
        .addTo(map);
      
      marker.getElement().addEventListener('click', (e) => {
        e.stopPropagation();
        setSelectedCity(city);
        showCityPopup(city);
      });
      
      cityMarkersRef.current.push(marker);
    });
  };

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

    clearCityMarkers();
    if (map.getSource('line-of-sight')) {
      map.removeLayer('line-of-sight');
      map.removeSource('line-of-sight');
    }

    map.addSource('line-of-sight', {
      type: 'geojson',
      data: {
        type: 'Feature',
        properties: {},
        geometry: {
          type: 'LineString',
          coordinates: data.line_coordinates.map(coord => [coord.lon, coord.lat])
        }
      }
    });

    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
      }
    });

    addMarkersToMap(data.conurbations);

    const bounds = new maplibregl.LngLatBounds();
    data.line_coordinates.forEach(coord => {
      bounds.extend([coord.lon, coord.lat]);
    });
    map.fitBounds(bounds, { padding: 50 });
  };

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

    const path = [];
    const steps = 50;
    const totalDistance = 20000;

    for (let i = 0; i <= steps; i++) {
      const dist = (totalDistance * i) / steps;
      path.push(calculateDestination(point.lat, point.lon, bearing, dist));
    }
    
    map.getSource('preview-line').setData({
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: path.map(p => [p.lon, p.lat])
      }
    });
  };

  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
    };
  };

  useEffect(() => {
    if (mapRef.current) {
      mapRef.current.setStyle(getMapStyle(mapStyle));
    }
  }, [mapStyle]);

  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
          }
        ]
      };
    }
    return style === 'dark' 
      ? 'https://demotiles.maplibre.org/style.json' 
      : 'https://demotiles.maplibre.org/style.json';
  };

  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);
    }
  };

  return (
    <div className="app-container">
      <div className="map-container" ref={mapContainerRef}>
      </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 === 'light' ? 'active-style' : ''} 
              onClick={() => setMapStyle('light')}
            >Light</button>
            <button 
              className={mapStyle === 'dark' ? 'active-style' : ''} 
              onClick={() => setMapStyle('dark')}
            >Dark</button>
            <button 
              className={mapStyle === 'satellite' ? 'active-style' : ''} 
              onClick={() => setMapStyle('satellite')}
            >Satellite</button>
          </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>Conurbations Found ({lineOfSightData.conurbations.length})</h3>
            <p className="hint">Click a city for details</p>
            <table>
              <thead>
                <tr>
                  <th>#</th>
                  <th>City</th>
                  <th>Population</th>
                  <th>Dist.</th>
                </tr>
              </thead>
              <tbody>
                {lineOfSightData.conurbations.map((city, index) => (
                  <tr 
                    key={city.id} 
                    onClick={() => {
                      setSelectedCity(city);
                      showCityPopup(city);
                      mapRef.current.flyTo({ center: [city.lon, city.lat], zoom: 8 });
                    }}
                    className={selectedCity?.id === city.id ? 'selected-row' : ''}
                  >
                    <td>{index + 1}</td>
                    <td>{city.name}</td>
                    <td>{(city.population / 1000).toFixed(0)}k</td>
                    <td>{city.distance_km}km</td>
                  </tr>
                ))}
              </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;
