import { useState, useEffect, useCallback, useRef } from 'react';
import { ConfigProvider, Spin, theme, Button, Tooltip, message, Result, Grid } from 'antd';
import { useNavigate } from 'react-router-dom';
import { AimOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons';
import { MapContainer, TileLayer, CircleMarker, useMap, useMapEvents } from 'react-leaflet';
import L, { type Map as LeafletMap } from 'leaflet';
import 'leaflet.heat';
import axios from 'axios';

// Extend Leaflet Map type to include private animation properties
declare module 'leaflet' {
  interface Map {
    _animatingZoom?: boolean;
    _moving?: boolean;
  }
}
import { useSettingsStore } from '@/stores/settings.store';
import PublicNavBar from '@/components/PublicNavBar';
import type { MapSettings, PublicCut, MapEvent } from '@/types/api';
import CutOverlays from '@/components/map/CutOverlays';
import CutOverlayControls from '@/components/map/CutOverlayControls';
import EventMarkers from '@/components/map/EventMarkers';
import EventLayerToggle from '@/components/map/EventLayerToggle';
import 'leaflet/dist/leaflet.css';

type BoundsQuery = {
  minLat: number;
  maxLat: number;
  minLng: number;
  maxLng: number;
};

/**
 * Public map point type — aggregated heatmap bucket, ~1.1km precision.
 * Individual resident locations are never exposed on the public map.
 */
type HeatPoint = { lat: number; lng: number; count: number };
type HeatmapResponse = { points: HeatPoint[] };

/**
 * HeatLayer: renders aggregated location buckets as a Leaflet heatmap overlay.
 * Uses leaflet.heat; maxCount is passed as `max` so colors scale to the current dataset.
 */
function HeatLayer({ points }: { points: HeatPoint[] }) {
  const map = useMap();
  useEffect(() => {
    if (!points.length) return;
    const maxCount = points.reduce((m, p) => (p.count > m ? p.count : m), 1);
    const layer = L.heatLayer(
      points.map((p) => [p.lat, p.lng, p.count] as [number, number, number]),
      { radius: 25, blur: 20, maxZoom: 14, max: maxCount }
    );
    layer.addTo(map);
    return () => {
      map.removeLayer(layer);
    };
  }, [map, points]);
  return null;
}

function FlyToPosition({ position }: { position: [number, number] }) {
  const map = useMap();
  useEffect(() => {
    map.flyTo(position, 17, { duration: 1.5 });
  }, [map, position]);
  return null;
}

function FullscreenInvalidator() {
  const map = useMap();
  useEffect(() => {
    const handler = () => {
      setTimeout(() => map.invalidateSize(), 100);
    };
    document.addEventListener('fullscreenchange', handler);
    document.addEventListener('webkitfullscreenchange', handler);
    return () => {
      document.removeEventListener('fullscreenchange', handler);
      document.removeEventListener('webkitfullscreenchange', handler);
    };
  }, [map]);
  return null;
}

function MapEventsHandler({ onMove }: { onMove: (map: LeafletMap) => void }) {
  const map = useMapEvents({
    moveend: () => {
      // Only trigger if not animating to prevent Leaflet state corruption
      if (!map._animatingZoom && !map._moving) {
        onMove(map);
      }
    },
    zoomend: () => {
      // Wait a tick for Leaflet to finish internal zoom state updates
      setTimeout(() => {
        if (!map._animatingZoom && !map._moving) {
          onMove(map);
        }
      }, 100);
    },
  });
  return null;
}

function CenterOnSettings({ settings }: { settings: MapSettings | null }) {
  const map = useMap();

  useEffect(() => {
    if (!settings?.latitude || !settings?.longitude) return;
    const lat = parseFloat(settings.latitude);
    const lng = parseFloat(settings.longitude);
    const zoom = settings.zoom ?? 12;

    // Use setView to imperatively update map center
    map.setView([lat, lng], zoom);
  }, [map, settings]);

  return null;
}

export default function MapPage() {
  const [settings, setSettings] = useState<MapSettings | null>(null);
  const [heatPoints, setHeatPoints] = useState<HeatPoint[]>([]);
  const [loading, setLoading] = useState(true);
  const [mapDisabled, setMapDisabled] = useState(false);
  const [loadingLocations, setLoadingLocations] = useState(false);
  const [cuts, setCuts] = useState<PublicCut[]>([]);
  const [visibleCutIds, setVisibleCutIds] = useState<Set<string>>(new Set());
  const [userPosition, setUserPosition] = useState<[number, number] | null>(null);
  const [flyTo, setFlyTo] = useState<[number, number] | null>(null);
  const [events, setEvents] = useState<MapEvent[]>([]);
  const [showEvents, setShowEvents] = useState(true);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  const fetchTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
  const abortControllerRef = useRef<AbortController | null>(null);
  const { settings: siteSettings } = useSettingsStore();
  const navigate = useNavigate();
  const screens = Grid.useBreakpoint();
  const isMobile = !screens.md;

  useEffect(() => {
    document.title = `Map | ${siteSettings?.organizationName || 'Changemaker'}`;
  }, [siteSettings?.organizationName]);

  const fetchLocations = useCallback(async (b: BoundsQuery) => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    setLoadingLocations(true);
    const controller = new AbortController();
    abortControllerRef.current = controller;

    try {
      const params = new URLSearchParams({
        minLat: b.minLat.toString(),
        maxLat: b.maxLat.toString(),
        minLng: b.minLng.toString(),
        maxLng: b.maxLng.toString(),
      });
      const res = await axios.get<HeatmapResponse>(`/api/map/locations/public?${params}`, {
        signal: controller.signal,
      });

      if (res.headers['x-location-bucket-limit-hit'] === 'true') {
        message.warning('Too many locations in view. Zoom in for more detail.', 3);
      }

      setHeatPoints(res.data.points ?? []);
    } catch (err) {
      if (!axios.isCancel(err)) {
        message.error('Failed to load locations');
      }
    } finally {
      setLoadingLocations(false);
      abortControllerRef.current = null;
    }
  }, []);

  const handleMapMove = useCallback((map: LeafletMap) => {
    const b = map.getBounds();
    const newBounds = {
      minLat: b.getSouth(),
      maxLat: b.getNorth(),
      minLng: b.getWest(),
      maxLng: b.getEast(),
    };

    clearTimeout(fetchTimerRef.current);
    fetchTimerRef.current = setTimeout(() => {
      fetchLocations(newBounds);
    }, 800); // Increased debounce to 800ms to allow zoom animations to complete
  }, [fetchLocations]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const [settingsRes, cutsRes] = await Promise.all([
          axios.get<MapSettings>('/api/map/settings'),
          axios.get<PublicCut[]>('/api/map/cuts/public'),
        ]);
        setSettings(settingsRes.data);
        if (settingsRes.data.publicMapEnabled === false) {
          setMapDisabled(true);
          setLoading(false);
          return;
        }
        if (settingsRes.data.publicShowCuts !== false) {
          setCuts(cutsRes.data);
          setVisibleCutIds(new Set(cutsRes.data.map((c) => c.id)));
        }
      } catch {
        // Still render map with defaults
      } finally {
        setLoading(false);
      }
    };
    fetchData();

    // Fetch events separately (fire-and-forget, non-blocking)
    axios.get<MapEvent[]>('/api/map/events/public')
      .then((res) => setEvents(res.data))
      .catch(() => { /* Events unavailable, map still works */ });
  }, []);

  // Clear user position after 30s
  useEffect(() => {
    if (userPosition) {
      const timer = setTimeout(() => setUserPosition(null), 30000);
      return () => clearTimeout(timer);
    }
  }, [userPosition]);

  // Track fullscreen state
  useEffect(() => {
    const handler = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', handler);
    document.addEventListener('webkitfullscreenchange', handler);
    return () => {
      document.removeEventListener('fullscreenchange', handler);
      document.removeEventListener('webkitfullscreenchange', handler);
    };
  }, []);

  const center: [number, number] = settings?.latitude && settings?.longitude
    ? [parseFloat(settings.latitude), parseFloat(settings.longitude)]
    : [45.4215, -75.6972];
  const zoom = settings?.zoom ?? 12;

  const handleGeolocate = useCallback(() => {
    if (!navigator.geolocation) {
      message.error('Geolocation not supported');
      return;
    }
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        const p: [number, number] = [pos.coords.latitude, pos.coords.longitude];
        setUserPosition(p);
        setFlyTo(p);
        setTimeout(() => setFlyTo(null), 2000);
      },
      () => message.error('Could not get your location'),
      { enableHighAccuracy: true, timeout: 10000 }
    );
  }, []);

  const handleFullscreen = useCallback(() => {
    const el = containerRef.current;
    if (!el) return;
    if (!document.fullscreenElement) {
      const requestFs = el.requestFullscreen || (el as any).webkitRequestFullscreen;
      if (requestFs) requestFs.call(el);
    } else {
      const exitFs = document.exitFullscreen || (document as any).webkitExitFullscreen;
      if (exitFs) exitFs.call(document);
    }
  }, []);

  const toggleCut = useCallback((id: string) => {
    setVisibleCutIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  if (mapDisabled) {
    return (
      <ConfigProvider
        theme={{
          algorithm: theme.darkAlgorithm,
          token: {
            colorPrimary: siteSettings?.publicColorPrimary ?? '#3498db',
            colorBgBase: siteSettings?.publicColorBgBase ?? '#0d1b2a',
            colorBgContainer: siteSettings?.publicColorBgContainer ?? '#1b2838',
          },
        }}
      >
        <div style={{ height: '100vh', display: 'flex', flexDirection: 'column', background: siteSettings?.publicColorBgBase ?? '#0d1b2a' }}>
          <PublicNavBar activePath="/map" />
          <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Result
              status="info"
              title="Map Unavailable"
              subTitle="The public map is currently not available."
              extra={
                <Button type="primary" onClick={() => navigate('/campaigns')}>
                  View Campaigns
                </Button>
              }
            />
          </div>
        </div>
      </ConfigProvider>
    );
  }

  return (
    <ConfigProvider
      theme={{
        algorithm: theme.darkAlgorithm,
        token: {
          colorPrimary: siteSettings?.publicColorPrimary ?? '#3498db',
          colorBgBase: siteSettings?.publicColorBgBase ?? '#0d1b2a',
          colorBgContainer: siteSettings?.publicColorBgContainer ?? '#1b2838',
          colorBgElevated: siteSettings?.publicColorBgContainer ?? '#1b2838',
          colorBorder: 'rgba(255,255,255,0.1)',
          colorBorderSecondary: 'rgba(255,255,255,0.06)',
          borderRadius: 8,
          colorLink: siteSettings?.publicColorPrimary ?? '#3498db',
        },
      }}
    >
      <div ref={containerRef} style={{ height: '100vh', display: 'flex', flexDirection: 'column', background: siteSettings?.publicColorBgBase ?? '#0d1b2a' }}>
        <PublicNavBar activePath="/map" />

        {/* Full-viewport map */}
        {loading ? (
          <div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
            <Spin size="large" />
          </div>
        ) : (
          <div style={{ flex: 1, position: 'relative' }}>
            {/* Floating controls */}
            <div
              style={{
                position: 'absolute',
                top: 10,
                right: 10,
                zIndex: 1000,
                display: 'flex',
                flexDirection: 'column',
                gap: 4,
              }}
            >
              <Tooltip title="My Location" placement="left">
                <Button
                  type="primary"
                  shape="circle"
                  size={isMobile ? 'large' : 'middle'}
                  icon={<AimOutlined />}
                  onClick={handleGeolocate}
                  style={{ background: 'rgba(27, 40, 56, 0.9)', borderColor: 'rgba(255,255,255,0.2)' }}
                />
              </Tooltip>
              <Tooltip title={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'} placement="left">
                <Button
                  type="primary"
                  shape="circle"
                  size={isMobile ? 'large' : 'middle'}
                  icon={isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
                  onClick={handleFullscreen}
                  style={{ background: 'rgba(27, 40, 56, 0.9)', borderColor: 'rgba(255,255,255,0.2)' }}
                />
              </Tooltip>
            </div>

            {loadingLocations && (
              <div style={{ position: 'absolute', top: 16, right: 16, zIndex: 1000 }}>
                <Spin size="small" />
              </div>
            )}

            <MapContainer
              center={center}
              zoom={zoom}
              style={{ width: '100%', height: '100%' }}
              scrollWheelZoom={true}
            >
              <FullscreenInvalidator />
              <CenterOnSettings settings={settings} />
              {settings?.publicShowLocations !== false && (
                <MapEventsHandler onMove={handleMapMove} />
              )}
              {flyTo && <FlyToPosition position={flyTo} />}
              <TileLayer
                attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              />

              {/* Cut overlays (render beneath markers) */}
              {settings?.publicShowCuts !== false && (
                <CutOverlays cuts={cuts} visibleCutIds={visibleCutIds} />
              )}

              {/* User position */}
              {userPosition && (
                <CircleMarker
                  center={userPosition}
                  radius={8}
                  pathOptions={{
                    fillColor: '#2196F3',
                    fillOpacity: 0.9,
                    color: '#fff',
                    weight: 2,
                    opacity: 1,
                  }}
                />
              )}

              {/* Gancio event markers */}
              {settings?.publicShowEvents !== false && showEvents && events.length > 0 && (
                <EventMarkers events={events} />
              )}

              {/* Aggregated heatmap (privacy-preserving: ~1.1km buckets, no PII). */}
              {settings?.publicShowLocations !== false && heatPoints.length > 0 && (
                <HeatLayer points={heatPoints} />
              )}
            </MapContainer>

            {/* Cut overlay controls */}
            {settings?.publicShowCuts !== false && cuts.length > 0 && (
              <CutOverlayControls
                cuts={cuts}
                visibleCutIds={visibleCutIds}
                onToggleCut={toggleCut}
                variant="public"
              />
            )}

            {/* Event layer toggle */}
            {settings?.publicShowEvents !== false && events.length > 0 && (
              <EventLayerToggle
                showEvents={showEvents}
                onToggle={setShowEvents}
                eventCount={events.length}
                variant="public"
              />
            )}
          </div>
        )}
      </div>
    </ConfigProvider>
  );
}
