import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Drawer, Spin, App, ConfigProvider, theme, Typography } from 'antd';
import { MapContainer, useMapEvents } from 'react-leaflet';
import MarkerClusterGroup from 'react-leaflet-cluster';
import type { Map as LeafletMap } from 'leaflet';
import 'leaflet/dist/leaflet.css';

// Extend Leaflet Map type to include private animation properties
declare module 'leaflet' {
  interface Map {
    _animatingZoom?: boolean;
    _moving?: boolean;
  }
}
import { useCanvassStore } from '@/stores/canvass.store';
import { useTrackingStore } from '@/stores/tracking.store';
import { useAuthStore } from '@/stores/auth.store';
import { useSettingsStore } from '@/stores/settings.store';
import { MAP_ADMIN_ROLES } from '@/types/api';
import type { RecordVisitPayload } from '@/types/canvass';
import VolunteerMapDrawer from '@/components/canvass/VolunteerMapDrawer';
import MapCrosshair from '@/components/canvass/MapCrosshair';
import CanvassMarkerGroup from '@/components/canvass/CanvassMarkerGroup';
import WalkingRouteLine from '@/components/canvass/WalkingRouteLine';
import GPSTracker from '@/components/canvass/GPSTracker';
import VisitRecordingForm from '@/components/canvass/VisitRecordingForm';
import LocationEditDrawer from '@/components/canvass/LocationEditDrawer';
import AddLocationDrawer from '@/components/canvass/AddLocationDrawer';
import CutOverlays from '@/components/map/CutOverlays';
import DynamicTileLayer from '@/components/map/DynamicTileLayer';
import NorthCompass from '@/components/map/NorthCompass';
import FriendsOnMap from '@/components/social/FriendsOnMap';
import VolunteerFooterNav from '@/components/VolunteerFooterNav';
import BottomControlPanel from '@/components/canvass/BottomControlPanel';
import { getPersistedTileLayer, persistTileLayer, getTileConfig } from '@/components/map/tileLayers';
import { createClusterIcon, CLUSTER_CONFIG } from '@/components/canvass/canvassClusterUtils';
const DEFAULT_CENTER: [number, number] = [45.42, -75.69];
const DEFAULT_ZOOM = 13;

function MapEventsHandler({ onMove, onZoom }: { onMove: (map: LeafletMap) => void; onZoom: (zoom: number) => 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) {
          onZoom(map.getZoom());
          onMove(map);
        }
      }, 100);
    },
  });
  return null;
}

export default function VolunteerMapPage() {
  const [searchParams] = useSearchParams();
  const { message, modal } = App.useApp();
  const { user } = useAuthStore();
  const { settings } = useSettingsStore();
  const trackingStore = useTrackingStore();

  const {
    mode, activeCutId, session,
    addresses, addressGroups,
    cuts, mapSettings,
    route, routeVisible, loadRoute, toggleRoute,
    selectedAddressId, selectAddress,
    gpsFollowing, setGpsFollowing,
    loadAllAddresses, loadCuts, loadMapSettings, loadSession,
    enterSessionMode, exitSessionMode,
    recordVisit, recordBulkVisit, goToNextUnitInBuilding, reset,
  } = useCanvassStore();

  const [loading, setLoading] = useState(true);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [menuDrawerHeight, setMenuDrawerHeight] = useState(0);
  const [bottomSheetOpen, setBottomSheetOpen] = useState(false);
  const [editDrawerOpen, setEditDrawerOpen] = useState(false);
  const [addDrawerOpen, setAddDrawerOpen] = useState(false);
  const [addLatLng, setAddLatLng] = useState<{ lat: number; lng: number } | null>(null);
  const [endingSession, setEndingSession] = useState(false);
  const [recording, setRecording] = useState(false);
  const [fullscreen, setFullscreen] = useState(false);
  const [visibleCutIds, setVisibleCutIds] = useState<Set<string>>(new Set());
  const [tileKey, setTileKey] = useState(getPersistedTileLayer);
  const [currentZoom, setCurrentZoom] = useState(DEFAULT_ZOOM);
  const mapRef = useRef<LeafletMap | null>(null);
  const gpsPositionRef = useRef<{ lat: number; lng: number } | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const drawerBodyRef = useRef<HTMLDivElement | null>(null);
  const fetchTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
  const abortControllerRef = useRef<AbortController | null>(null);

  const userRole = user?.role ?? 'TEMP';
  const isAdmin = MAP_ADMIN_ROLES.includes(userRole);
  const sessionActive = mode === 'session' && !!session;

  // Footer nav height for positioning (no session bar, controls integrated into bottom panel)
  const FOOTER_HEIGHT = 44;

  // ─── Initialize ──────────────────────────────────────────────────
  useEffect(() => {
    const init = async () => {
      setLoading(true);
      try {
        await Promise.all([loadAllAddresses(), loadCuts(), loadMapSettings()]);

        // Check for existing active session
        await loadSession();
        const state = useCanvassStore.getState();

        // Handle URL query params
        const cutIdParam = searchParams.get('cutId');
        const shiftIdParam = searchParams.get('shiftId') ?? undefined;
        if (cutIdParam && !state.session) {
          await enterSessionMode(cutIdParam, shiftIdParam);
        } else if (state.session) {
          // Re-load cut-filtered addresses for existing session
          const store = useCanvassStore.getState();
          if (store.session) {
            await useCanvassStore.getState().loadAddresses(store.session.cutId);
            useCanvassStore.setState({ mode: 'session', activeCutId: store.session.cutId });
          }
        }

        // Start GPS tracking (restore existing or create new)
        await trackingStore.restoreSession();
        if (!useTrackingStore.getState().isTracking) {
          await trackingStore.startTracking();
        }
      } catch {
        message.error('Failed to load map data');
      } finally {
        setLoading(false);
      }
    };

    init();

    return () => {
      trackingStore.stopTracking();
      reset();
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  // Initialize visible cuts when cuts load
  useEffect(() => {
    if (cuts.length > 0) {
      setVisibleCutIds(new Set(cuts.map((c) => c.id)));
    }
  }, [cuts]);

  // Fit map to cut bounds in session mode
  useEffect(() => {
    if (!sessionActive || !activeCutId || !mapRef.current) return;
    const cut = cuts.find((c) => c.id === activeCutId);
    if (!cut?.bounds) return;
    try {
      const bounds = JSON.parse(cut.bounds);
      mapRef.current.fitBounds(
        [[bounds.minLat, bounds.minLng], [bounds.maxLat, bounds.maxLng]],
        { padding: [20, 20] },
      );
    } catch { /* ignore */ }
  }, [sessionActive, activeCutId, cuts]);

  // Center map on settings when in free mode on load
  useEffect(() => {
    if (loading || sessionActive || !mapSettings || !mapRef.current) return;
    const lat = mapSettings.latitude ? parseFloat(mapSettings.latitude) : null;
    const lng = mapSettings.longitude ? parseFloat(mapSettings.longitude) : null;
    const zoom = mapSettings.zoom ?? DEFAULT_ZOOM;
    if (lat && lng) {
      mapRef.current.setView([lat, lng], zoom);
    }
  }, [loading, sessionActive, mapSettings]);

  // ─── Map Move Handler (Viewport Loading) ──────────────────────────
  const handleMapMove = useCallback((map: LeafletMap) => {
    const bounds = map.getBounds();
    const zoom = map.getZoom();

    // Add 10% padding like v1 for smoother panning
    const padded = bounds.pad(0.1);
    const newBounds = {
      minLat: padded.getSouth(),
      maxLat: padded.getNorth(),
      minLng: padded.getWest(),
      maxLng: padded.getEast(),
    };

    // Zoom-based limits (from v1 optimization)
    const limit = zoom >= 15 ? 5000 : zoom >= 12 ? 3000 : 1500;

    clearTimeout(fetchTimerRef.current);
    fetchTimerRef.current = setTimeout(() => {
      // Cancel any pending request
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
      abortControllerRef.current = new AbortController();

      if (mode === 'session' && activeCutId) {
        useCanvassStore.getState().loadAddresses(activeCutId, newBounds, limit);
      } else {
        useCanvassStore.getState().loadAllAddresses(newBounds, limit);
      }
    }, 1500); // Increased debounce to 1500ms to allow longer queries to complete
  }, [mode, activeCutId]);

  // ─── Handlers ────────────────────────────────────────────────────

  const handleMarkerClick = useCallback((addressId: string) => {
    selectAddress(addressId);
    setBottomSheetOpen(true);

    // Auto-zoom to unclustered view if selecting a marker in a cluster
    // This ensures the white selection ring is visible
    const group = addressGroups.find(g =>
      g.addresses.some(a => a.id === addressId)
    );
    if (group && mapRef.current) {
      const currentZoom = mapRef.current.getZoom();
      if (currentZoom < 18) {
        mapRef.current.setView([group.latitude, group.longitude], 18, { animate: true });
      }
    }
  }, [selectAddress, addressGroups]);

  const handleRecordVisit = useCallback(async (payload: RecordVisitPayload) => {
    setRecording(true);
    try {
      await recordVisit(payload);
      // Track event point for visit
      const pos = gpsPositionRef.current;
      if (pos) trackingStore.addEventPoint(pos.lat, pos.lng, 'VISIT_RECORDED');
      message.success('Visit recorded');
      setBottomSheetOpen(false);
      // Clusters update reactively via addressGroups reference change
      // No need to force refresh - React will detect the state change
    } catch {
      message.error('Failed to record visit');
    } finally {
      setRecording(false);
    }
  }, [recordVisit, message, trackingStore]);

  const handleNextDoor = useCallback(() => {
    const unvisited = addresses.filter((a) => !a.lastVisit);
    if (unvisited.length === 0) {
      message.info('All addresses visited!');
      return;
    }

    let target = unvisited[0]!;
    if (gpsPositionRef.current) {
      let nearest = Infinity;
      for (const addr of unvisited) {
        const dx = addr.location.latitude - gpsPositionRef.current.lat;
        const dy = addr.location.longitude - gpsPositionRef.current.lng;
        const dist = dx * dx + dy * dy;
        if (dist < nearest) {
          nearest = dist;
          target = addr;
        }
      }
    }

    selectAddress(target.id);
    setBottomSheetOpen(true);
    mapRef.current?.setView([target.location.latitude, target.location.longitude], 18, { animate: true });
  }, [addresses, selectAddress, message]);

  const handleGpsPositionChange = useCallback((lat: number, lng: number) => {
    gpsPositionRef.current = { lat, lng };
  }, []);

  const handleEndSession = useCallback(async () => {
    modal.confirm({
      title: 'End Canvass Session?',
      content: 'Your progress will be saved. You can start a new session later.',
      okText: 'End Session',
      okType: 'danger',
      onOk: async () => {
        setEndingSession(true);
        try {
          const pos = gpsPositionRef.current;
          if (pos) trackingStore.addEventPoint(pos.lat, pos.lng, 'SESSION_ENDED');
          await exitSessionMode();
          message.success('Session ended');
          if (mapSettings && mapRef.current) {
            const lat = mapSettings.latitude ? parseFloat(mapSettings.latitude) : null;
            const lng = mapSettings.longitude ? parseFloat(mapSettings.longitude) : null;
            if (lat && lng) {
              mapRef.current.setView([lat, lng], mapSettings.zoom ?? DEFAULT_ZOOM);
            }
          }
        } catch {
          message.error('Failed to end session');
        } finally {
          setEndingSession(false);
        }
      },
    });
  }, [exitSessionMode, message, modal, mapSettings]);

  const handleStartSession = useCallback(async (cutId: string, shiftId?: string) => {
    try {
      const pos = gpsPositionRef.current;
      await enterSessionMode(cutId, shiftId, pos?.lat, pos?.lng);
      // Link canvass session to tracking + add event
      const canvassSession = useCanvassStore.getState().session;
      if (canvassSession) {
        await trackingStore.linkCanvassSession(canvassSession.id);
        if (pos) trackingStore.addEventPoint(pos.lat, pos.lng, 'SESSION_STARTED');
      }
      message.success('Session started');
    } catch {
      message.error('Failed to start session');
    }
  }, [enterSessionMode, message, trackingStore]);

  const handleTileChange = useCallback((key: string) => {
    setTileKey(key);
    persistTileLayer(key);
  }, []);

  const handleSearchFlyTo = useCallback((lat: number, lng: number) => {
    mapRef.current?.flyTo([lat, lng], 18);
    setGpsFollowing(false);
  }, [setGpsFollowing]);

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

  // "+" tapped — read map center from the crosshair position
  const handleAddAtCenter = useCallback(() => {
    if (!mapRef.current) return;
    const center = mapRef.current.getCenter();
    setAddLatLng({ lat: center.lat, lng: center.lng });
    setAddDrawerOpen(true);
  }, []);

  const handleToggleFullscreen = useCallback(() => {
    if (!containerRef.current) return;
    if (!document.fullscreenElement) {
      (containerRef.current.requestFullscreen?.() ??
        (containerRef.current as unknown as { webkitRequestFullscreen?: () => Promise<void> }).webkitRequestFullscreen?.());
      setFullscreen(true);
    } else {
      document.exitFullscreen();
      setFullscreen(false);
    }
  }, []);

  useEffect(() => {
    const handler = () => { if (!document.fullscreenElement) setFullscreen(false); };
    document.addEventListener('fullscreenchange', handler);
    return () => document.removeEventListener('fullscreenchange', handler);
  }, []);

  // Measure menu drawer height when open
  useEffect(() => {
    if (!drawerOpen || !drawerBodyRef.current) {
      setMenuDrawerHeight(0);
      return;
    }

    const observer = new ResizeObserver((entries) => {
      const height = entries[0]?.contentRect.height ?? 0;
      // Cap at 60vh to ensure map is always visible
      setMenuDrawerHeight(Math.min(height, window.innerHeight * 0.6));
    });

    observer.observe(drawerBodyRef.current);
    return () => observer.disconnect();
  }, [drawerOpen]);

  // Find selected address and its group
  const selectedAddress = addresses.find((a) => a.id === selectedAddressId) ?? null;
  const selectedGroup = selectedAddress
    ? addressGroups.find((g) => g.addresses.some((a) => a.id === selectedAddressId))
    : null;
  const visitedCount = addresses.filter((a) => a.lastVisit).length;
  const unvisitedInBuilding = selectedGroup
    ? selectedGroup.addresses.filter((a) => !a.lastVisit).length
    : 0;

  const center: [number, number] = mapSettings?.latitude && mapSettings?.longitude
    ? [parseFloat(mapSettings.latitude), parseFloat(mapSettings.longitude)]
    : DEFAULT_CENTER;
  const zoom = mapSettings?.zoom ?? DEFAULT_ZOOM;

  // Memoize cluster config with zoom-aware radius
  const memoizedClusterConfig = useMemo(() => {
    // Zoom-aware cluster radius (like v1)
    const maxClusterRadius = currentZoom >= 15 ? 30 : currentZoom >= 12 ? 50 : 80;

    return {
      ...CLUSTER_CONFIG,
      maxClusterRadius,
      iconCreateFunction: createClusterIcon,
    };
  }, [currentZoom]);

  if (loading) {
    return (
      <div style={{ height: '100dvh', display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#0d1b2a' }}>
        <Spin size="large" />
      </div>
    );
  }

  return (
    <ConfigProvider
      theme={{
        algorithm: theme.darkAlgorithm,
        token: {
          colorPrimary: settings?.publicColorPrimary ?? '#3498db',
          colorBgBase: settings?.publicColorBgBase ?? '#0d1b2a',
          colorBgContainer: settings?.publicColorBgContainer ?? '#1b2838',
          colorBgElevated: settings?.publicColorBgContainer ?? '#1b2838',
          borderRadius: 8,
        },
      }}
    >
      <App>
        <div
          ref={containerRef}
          style={{
            position: 'relative',
            width: '100vw',
            height: drawerOpen
              ? `calc(100dvh - ${FOOTER_HEIGHT}px - ${menuDrawerHeight}px)`
              : '100dvh',
            transition: 'height 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
            overflow: 'hidden',
          }}
        >

          <MapContainer
            center={center}
            zoom={zoom}
            style={{ width: '100%', height: '100%' }}
            zoomControl={false}
            ref={mapRef}
          >
            <MapEventsHandler onMove={handleMapMove} onZoom={setCurrentZoom} />
            <DynamicTileLayer config={getTileConfig(tileKey)} />

            {/* Cut polygon overlays */}
            <CutOverlays cuts={cuts} visibleCutIds={visibleCutIds} />

            {/* Address markers (grouped by building, clustered at low zoom) */}
            <MarkerClusterGroup
              key={currentZoom >= 18 ? 'unclustered' : 'clustered'}
              {...memoizedClusterConfig}
            >
              {addressGroups.map((group) => (
                <CanvassMarkerGroup
                  key={group.locationId}
                  group={group}
                  selectedAddressId={selectedAddressId}
                  onAddressClick={handleMarkerClick}
                />
              ))}
            </MarkerClusterGroup>

            {/* Walking route (session mode) */}
            {sessionActive && routeVisible && route && <WalkingRouteLine route={route} />}

            {/* GPS tracker */}
            <GPSTracker
              following={gpsFollowing}
              onPositionChange={handleGpsPositionChange}
            />
          </MapContainer>

          {/* Friends currently canvassing overlay */}
          <FriendsOnMap />

          {/* Persistent crosshair at map center — tap center dot to add location */}
          <MapCrosshair onClick={handleAddAtCenter} />

          {/* North compass — bottom-left, just above footer */}
          <NorthCompass style={{ bottom: FOOTER_HEIGHT + 44 + 12, top: 'auto', left: 8 }} />

          {/* Bottom control panel — all controls consolidated */}
          <BottomControlPanel
            activeTileKey={tileKey}
            onTileChange={handleTileChange}
            sessionActive={sessionActive}
            visitedCount={visitedCount}
            totalCount={addresses.length}
            routeVisible={routeVisible}
            gpsFollowing={gpsFollowing}
            fullscreen={fullscreen}
            cuts={cuts}
            visibleCutIds={visibleCutIds}
            onNextDoor={handleNextDoor}
            onToggleRoute={() => {
              if (!routeVisible && activeCutId) {
                const pos = gpsPositionRef.current;
                loadRoute(activeCutId, true, pos?.lat, pos?.lng);
              }
              toggleRoute();
            }}
            onToggleGps={() => setGpsFollowing(!gpsFollowing)}
            onToggleFullscreen={handleToggleFullscreen}
            onAddAtCenter={handleAddAtCenter}
            onToggleCut={handleToggleCut}
            onSearchFlyTo={handleSearchFlyTo}
            bottomOffset={FOOTER_HEIGHT}
          />

          {/* Footer nav — stays at bottom, sits above drawer */}
          <VolunteerFooterNav
            onMenuOpen={() => setDrawerOpen(!drawerOpen)}
            menuActive={drawerOpen}
            style={{
              position: 'absolute',
              bottom: 0,
              left: 0,
              right: 0,
              zIndex: 1200, // Above drawer (1150)
            }}
          />

          {/* Bottom drawer — menu, stats, assignments, session picker */}
          <VolunteerMapDrawer
            open={drawerOpen}
            onClose={() => setDrawerOpen(false)}
            drawerBodyRef={drawerBodyRef}
            cuts={cuts}
            onStartSession={handleStartSession}
            sessionActive={sessionActive}
            sessionCutName={session?.cut?.name}
            sessionStartedAt={session?.startedAt}
            onEndSession={handleEndSession}
            endingSession={endingSession}
            activeCutId={activeCutId ?? undefined}
            activeShiftId={session?.shiftId ?? undefined}
            isAdmin={isAdmin}
          />

          {/* Bottom sheet — visit recording */}
          <Drawer
            placement="bottom"
            open={bottomSheetOpen && !!selectedAddress}
            onClose={() => { setBottomSheetOpen(false); selectAddress(null); }}
            height="auto"
            zIndex={1200}
            title={
              selectedAddress && (
                <div style={{ textAlign: 'center', paddingBottom: 8 }}>
                  {/* Drag handle indicator */}
                  <div
                    style={{
                      width: 40,
                      height: 4,
                      background: 'rgba(255,255,255,0.3)',
                      borderRadius: 2,
                      margin: '0 auto 12px',
                    }}
                  />
                  <Typography.Text strong>
                    {selectedAddress.location.address}
                    {selectedAddress.unitNumber && ` - Unit ${selectedAddress.unitNumber}`}
                  </Typography.Text>
                </div>
              )
            }
            styles={{
              header: { padding: '12px 16px 0' },
              body: {
                padding: '12px 16px',
                maxHeight: `calc(100dvh - ${FOOTER_HEIGHT + 120}px - env(safe-area-inset-bottom))`,
                overflow: 'auto',
              },
            }}
            maskClosable
          >
            {selectedAddress && (
              <>
                <VisitRecordingForm
                  address={selectedAddress}
                  sessionId={session?.id}
                  shiftId={session?.shiftId ?? undefined}
                  onRecord={handleRecordVisit}
                  onBulkRecord={async (payload) => {
                    await recordBulkVisit(payload);
                  }}
                  onNextUnit={goToNextUnitInBuilding}
                  recording={recording}
                  userRole={userRole}
                  isMultiUnit={selectedGroup?.isMultiUnit}
                  unvisitedCountInBuilding={unvisitedInBuilding}
                />
                {isAdmin && (
                  <div style={{ marginTop: 8, textAlign: 'center' }}>
                    <a
                      onClick={() => { setBottomSheetOpen(false); setEditDrawerOpen(true); }}
                      style={{ fontSize: 12 }}
                    >
                      Edit Address Details
                    </a>
                  </div>
                )}
              </>
            )}
          </Drawer>

          {/* Bottom sheet — address editing (admin only) */}
          <LocationEditDrawer
            open={editDrawerOpen}
            onClose={() => setEditDrawerOpen(false)}
            location={selectedAddress as any} // TODO: Update LocationEditDrawer to work with Address
            zIndex={1200}
          />

          {/* Bottom sheet — add new location + record visit */}
          <AddLocationDrawer
            open={addDrawerOpen}
            onClose={() => { setAddDrawerOpen(false); setAddLatLng(null); }}
            lat={addLatLng?.lat ?? null}
            lng={addLatLng?.lng ?? null}
            userRole={userRole}
            sessionId={session?.id}
            shiftId={session?.shiftId ?? undefined}
            zIndex={1200}
          />
        </div>
      </App>
    </ConfigProvider>
  );
}
