import React, { useState, useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
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 [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 [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('load', () => {
      console.log('Map loaded successfully');
      
      // Add atmosphere/sky effects (MapLibre v5 uses setSky)
      mapRef.current.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
      });
      
      // Initialize start marker
      startMarkerRef.current = new maplibregl.Marker({ color: '#FF6B6B' })
        .setLngLat([selectedPoint.lon, selectedPoint.lat])
        .addTo(mapRef.current);

      // Initialize preview line source and layer
      mapRef.current.addSource('preview-line', {
        type: 'geojson',
        data: {
          type: 'Feature',
          geometry: {
            type: 'LineString',
            coordinates: []
          }
        }
      });

      mapRef.current.addLayer({
        id: 'preview-line',
        type: 'line',
        source: 'preview-line',
        paint: {
          'line-color': '#FF6B6B',
          'line-width': 2,
          'line-dasharray': [2, 2]
        }
      });

      updatePreviewLine(selectedPoint, direction);
    });

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

  // 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);
    }
    
    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 startFlyOver = () => {
    if (!lineOfSightData || !mapRef.current) return;
    
    if (isPlaying) {
      if (animationRef.current) cancelAnimationFrame(animationRef.current);
      setIsPlaying(false);
      return;
    }

    setIsPlaying(true);
    
    const coordinates = lineOfSightData.line_coordinates.map(c => [c.lon, c.lat]);
    const route = turf.lineString(coordinates);
    const duration = 20000; // 20 seconds for the full flyover
    const startTime = performance.now();
    const routeDistance = turf.length(route);

    function animate(currentTime) {
      if (!mapRef.current) return;
      
      const elapsed = currentTime - startTime;
      const phase = Math.min(elapsed / duration, 1); // 0 to 1
      
      // Stop when we reach the end
      if (phase >= 1) {
        setIsPlaying(false);
        return;
      }
      
      const currentDist = routeDistance * phase;
      
      // Look 100km ahead, camera is at the current distance
      const target = turf.along(route, Math.min(currentDist + 100, routeDistance)).geometry.coordinates;
      const eye = turf.along(route, currentDist).geometry.coordinates;

      const camera = mapRef.current.getFreeCameraOptions();
      
      // Position camera 50,000m (50km) above the 'eye' point
      camera.position = maplibregl.MercatorCoordinate.fromLngLat(
          { lng: eye[0], lat: eye[1] }, 
          50000 
      );
      
      // Look at the target point
      camera.lookAtPoint({ lng: target[0], lat: target[1] });

      mapRef.current.setFreeCameraOptions(camera);
      
      animationRef.current = requestAnimationFrame(animate);
    }
    
    animationRef.current = requestAnimationFrame(animate);
  };

  useEffect(() => {
    // Update preview whenever point or direction changes
    if (mapRef.current && mapRef.current.isStyleLoaded()) {
      updatePreviewLine(selectedPoint, direction);
    }
  }, [selectedPoint, direction]);

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

    // Generate 50 points along a 20,000km great circle path for a smooth curve
    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])
      }
    });
  };

  // Helper to calculate destination point given start, bearing, and distance (km)
  const calculateDestination = (lat, lon, bearing, distance) => {
    const R = 6371; // Earth's radius in km
    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(() => {
    // Update map style when toggle changes
    if (mapRef.current) {
      mapRef.current.setStyle(getMapStyle(mapStyle));
    }
  }, [mapStyle]);

  const getMapStyle = (style) => {
    return style === 'dark' 
      ? 'https://demotiles.maplibre.org/style.json' 
      : 'https://demotiles.maplibre.org/style.json'; // Using same for now, can be customized
  };

  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); // Lock the map after showing results
      
      // Hide the preview line when showing final results
      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 renderLineOnMap = (data) => {
    const map = mapRef.current;
    if (!map) return;

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

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

    // Add line layer
    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
      }
    });

    // Add city markers
    data.conurbations.forEach((city, index) => {
      const displayIndex = index + 1;
      
      // Create marker element
      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>
      `;

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

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

  return (
    <div className="app-container">
      <div className="map-container" ref={mapContainerRef}>
        {selectedCity && (
          <div className="info-panel-modal">
            <div className="info-panel-content">
              <button className="close-btn" onClick={() => setSelectedCity(null)}>&times;</button>
              <h2>{selectedCity.name}</h2>
              <div className="info-grid">
                <div className="info-item">
                  <label>Country:</label>
                  <span>{selectedCity.country}</span>
                </div>
                <div className="info-item">
                  <label>Population:</label>
                  <span>{selectedCity.population.toLocaleString()}</span>
                </div>
                <div className="info-item">
                  <label>Coordinates:</label>
                  <span>{selectedCity.lat.toFixed(4)}, {selectedCity.lon.toFixed(4)}</span>
                </div>
                <div className="info-item">
                  <label>Distance from Start:</label>
                  <span>{selectedCity.distance_km} km</span>
                </div>
                <div className="info-item">
                  <label>Deviation from Line:</label>
                  <span>{selectedCity.off_line_km} km</span>
                </div>
              </div>
              <button className="action-btn-small" onClick={() => {
                mapRef.current.flyTo({ center: [selectedCity.lon, selectedCity.lat], zoom: 8 });
              }}>
                📍 Go to City
              </button>
            </div>
          </div>
        )}
      </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 onClick={() => setMapStyle('light')}>Light</button>
            <button onClick={() => setMapStyle('dark')}>Dark</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' : '✈️ 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)}
                    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;
