import React, { useState, useRef, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { MapNode } from '@/components/MapNode';
import { SystemContextMenu } from '@/components/SystemContextMenu';
import { Connection } from '@/components/Connection';
import { useRegionData } from '@/hooks/useRegionData';
import { loadWormholeSystems, saveWormholeSystem, deleteWormholeSystem } from '@/utils/wormholeStorage';
import { System, Position, Connection as ConnectionType } from '@/lib/types';
import { getSecurityColor } from '@/utils/securityColors';
import { Header } from './Header';
import { ListCharacters, StartESILogin, SetDestinationForAll, PostRouteForAllByNames, GetCharacterLocations } from 'wailsjs/go/main/App';
import { toast } from '@/hooks/use-toast';
import { getSystemsRegions } from '@/utils/systemApi';
import { useSystemJumps, useSystemKills } from '@/hooks/useSystemStatistics';
import { StatisticsToggle } from './StatisticsToggle';

// Interaction/indicator constants
const SELECT_HOLD_MS = 300;
const PAN_THRESHOLD_PX = 6;
const DRAG_SNAP_DISTANCE = 20;
const VIA_WAYPOINT_RING_RADIUS = 14;
const VIA_WAYPOINT_RING_COLOR = '#10b981';
const INDICATED_RING_RADIUS = 20;
const INDICATED_RING_COLOR = '#f59e0b';
const INDICATED_RING_ANIM_VALUES = '18;22;18';
const INDICATED_RING_ANIM_DUR = '1.2s';
const SHIFT_SELECT_STROKE_COLOR = '#60a5fa';
const SHIFT_SELECT_FILL_COLOR = 'rgba(96,165,250,0.12)';
const SHIFT_SELECT_STROKE_WIDTH = 2;

interface RegionMapProps {
  regionName: string;
  focusSystem?: string;
  isCompact?: boolean;
  isWormholeRegion?: boolean;
}

interface ContextMenuState {
  x: number,
  y: number,
  system: System
}

function computeNodePositions(systems: Map<string, System>): Record<string, Position> {
  const nodePositions: Record<string, Position> = {};
  systems.forEach((system, key) => {
    nodePositions[key] = { x: system.x, y: system.y };
  });
  return nodePositions;
}
function computeNodeConnections(systems: Map<string, System>): Map<string, ConnectionType> {
  const connections: Map<string, ConnectionType> = new Map();
  for (const [key, system] of systems.entries()) {
    const connectedSystems = system.connectedSystems.split(',');
    for (let connectedSystem of connectedSystems) {
      connectedSystem = connectedSystem.trim();
      const connectionKey = [key, connectedSystem].sort().join('-');
      const destinationSystem = systems.get(connectedSystem);
      if (destinationSystem) {
        const averageSecurity = ((system.security || 0) + (destinationSystem.security || 0)) / 2;
        const connection = {
          key: connectionKey,
          from: { x: system.x, y: system.y },
          to: { x: destinationSystem.x, y: destinationSystem.y },
          color: getSecurityColor(averageSecurity)
        }
        connections.set(connectionKey, connection);
      } else {
        console.log(`Destination system ${connectedSystem} not found for ${key}`);
      }
    }
  }
  return connections;
}

// Cache of region -> Map(systemName -> System) from region JSONs
const regionSystemsCache: Map<string, Map<string, System>> = new Map();
// Cache of universe region centroids (regionName -> {x, y})
const universeRegionPosCache: Map<string, { x: number; y: number }> = new Map();
let universeLoaded = false;
const ensureUniversePositions = async () => {
  if (universeLoaded) return;
  try {
    const resp = await fetch('/universe.json');
    if (!resp.ok) return;
    const regions: Array<{ regionName: string; x: string; y: string; security: number; connectsTo: string }> = await resp.json();
    for (const r of regions) {
      universeRegionPosCache.set(r.regionName, { x: parseInt(r.x, 10), y: parseInt(r.y, 10) });
    }
    universeLoaded = true;
  } catch (_) {
    // ignore
  }
};

export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormholeRegion = false }: RegionMapProps) => {
  const navigate = useNavigate();
  const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 1200, height: 800 });
  const [isPanning, setIsPanning] = useState(false);
  const [lastPanPoint, setLastPanPoint] = useState({ x: 0, y: 0 });
  const [draggingNode, setDraggingNode] = useState<string | null>(null);
  const [tempConnection, setTempConnection] = useState<{ from: Position; to: Position } | null>(null);
  const [systems, setSystems] = useState<Map<string, System>>(new Map());
  const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
  const [connections, setConnections] = useState<Map<string, ConnectionType>>(new Map());
  const [positions, setPositions] = useState<Record<string, Position>>({});
  const svgRef = useRef<SVGSVGElement>(null);

  const [viaMode, setViaMode] = useState(false);
  const [viaDest, setViaDest] = useState<string | null>(null);
  const [viaQueue, setViaQueue] = useState<string[]>([]);

  type OffIndicator = { from: string; toRegion: string; count: number; color: string; angle: number; sampleTo?: string };
  const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]);
  const [meanNeighborAngle, setMeanNeighborAngle] = useState<Record<string, number>>({});
  const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
  const [focusUntil, setFocusUntil] = useState<number | null>(null);

  // Statistics state - MUST default to false to avoid API spam!
  const [showJumps, setShowJumps] = useState(false);
  const [showKills, setShowKills] = useState(false);
  
  // Cache for system name to ID mappings
  const [systemIDCache, setSystemIDCache] = useState<Map<string, number>>(new Map());

  // New: selection/aim state for left-click aimbot behavior
  const [isSelecting, setIsSelecting] = useState(false);
  const [indicatedSystem, setIndicatedSystem] = useState<string | null>(null);
  const selectTimerRef = useRef<number | null>(null);
  const downClientPointRef = useRef<{ x: number; y: number } | null>(null);
  const mouseButtonRef = useRef<number | null>(null);

  // New: shift-drag circle selection state (VIA mode)
  const [shiftSelecting, setShiftSelecting] = useState(false);
  const [shiftCenter, setShiftCenter] = useState<Position | null>(null);
  const [shiftRadius, setShiftRadius] = useState<number>(0);

  // Interaction state machine (lightweight)
  type InteractionMode = 'idle' | 'holding' | 'panning' | 'selecting' | 'shiftSelecting';
  const [mode, setMode] = useState<InteractionMode>('idle');

  // When focusSystem changes, set an expiry 20s in the future
  useEffect(() => {
    if (focusSystem) {
      setFocusUntil(Date.now() + 20000);
    }
  }, [focusSystem]);

  // Timer to clear focus after expiry
  useEffect(() => {
    if (!focusUntil) return;
    const id = setInterval(() => {
      if (Date.now() > focusUntil) {
        setFocusUntil(null);
      }
    }, 500);
    return () => clearInterval(id);
  }, [focusUntil]);

  useEffect(() => {
    const onKeyDown = async (e: KeyboardEvent) => {
      if (e.key === 'Escape' && viaMode) {
        try {
          if (!(await ensureAnyLoggedIn())) return;
          if (viaDest) {
            await PostRouteForAllByNames(viaDest, viaQueue);
            toast({ title: 'Route set', description: `${viaDest}${viaQueue.length ? ' via ' + viaQueue.join(', ') : ''}` });
          }
        } catch (err: any) {
          toast({ title: 'Failed to set route', description: String(err), variant: 'destructive' });
        } finally {
          setViaMode(false);
          setViaDest(null);
          setViaQueue([]);
        }
      }
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [viaMode, viaDest, viaQueue]);

  const { data: rsystems, isLoading, error } = useRegionData(regionName);
  
  // Fetch statistics data - only when toggles are enabled
  const { data: jumpsData } = useSystemJumps(showJumps);
  const { data: killsData } = useSystemKills(showKills);

  useEffect(() => {
    if (!isLoading && error == null && rsystems && rsystems.size > 0)
      setSystems(rsystems);
  }, [rsystems, isLoading, error]);

  // For now, we'll use a simplified approach without system ID resolution
  // The ESI data will be displayed for systems that have data, but we won't
  // be able to match system names to IDs until the binding issue is resolved

  useEffect(() => {
    if (!systems || systems.size === 0) return;
    const positions = computeNodePositions(systems);
    setPositions(positions);
    const connections = computeNodeConnections(systems);
    setConnections(connections);
    // Compute per-system mean outbound angle in screen coords (atan2(dy,dx)) to in-region neighbors
    const angleMap: Record<string, number> = {};
    systems.forEach((sys, name) => {
      const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
      let sumSin = 0, sumCos = 0, count = 0;
      for (const n of neighbors) {
        const neighbor = systems.get(n);
        if (!neighbor) continue;
        const dx = neighbor.x - sys.x;
        const dy = neighbor.y - sys.y; // y-down screen
        const a = Math.atan2(dy, dx);
        sumSin += Math.sin(a);
        sumCos += Math.cos(a);
        count++;
      }
      if (count > 0) {
        angleMap[name] = Math.atan2(sumSin, sumCos); // average angle
      }
    });
    setMeanNeighborAngle(angleMap);
  }, [systems]);

  // Poll character locations every 7s and store those in this region
  useEffect(() => {
    let timer: any;
    const tick = async () => {
      try {
        const locs = await GetCharacterLocations();
        const here = locs.filter(l => !!l.solar_system_name && systems.has(l.solar_system_name));
        setCharLocs(here.map(l => ({ character_id: l.character_id, character_name: l.character_name, solar_system_name: l.solar_system_name })));
      } catch (_) {
        // ignore
      } finally {
        timer = setTimeout(tick, 7000);
      }
    };
    tick();
    return () => { if (timer) clearTimeout(timer); };
  }, [systems]);

  // Compute off-region indicators: dedupe per (from, toRegion), compute avg color, and angle via universe centroids
  useEffect(() => {
    const computeOffRegion = async () => {
      if (!systems || systems.size === 0) { setOffRegionIndicators([]); return; }

      const toLookup: Set<string> = new Set();
      for (const [, sys] of systems.entries()) {
        const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
        for (const n of neighbors) if (!systems.has(n)) toLookup.add(n);
      }
      if (toLookup.size === 0) { setOffRegionIndicators([]); return; }

      const nameToRegion = await getSystemsRegions(Array.from(toLookup));

      // Cache remote region systems (for security values) and universe positions
      const neededRegions = new Set<string>();
      for (const n of Object.keys(nameToRegion)) {
        const r = nameToRegion[n];
        if (!r) continue;
        if (!regionSystemsCache.has(r)) neededRegions.add(r);
      }
      if (neededRegions.size > 0) {
        await Promise.all(Array.from(neededRegions).map(async (r) => {
          try {
            const resp = await fetch(`/${encodeURIComponent(r)}.json`);
            if (!resp.ok) return;
            const systemsList: System[] = await resp.json();
            const m = new Map<string, System>();
            systemsList.forEach(s => m.set(s.solarSystemName, s));
            regionSystemsCache.set(r, m);
          } catch (_) { /* noop */ }
        }));
      }
      await ensureUniversePositions();

      // Build indicators: group by from+toRegion; angle from local geometry only (meanNeighborAngle + PI)
      type Agg = { from: string; toRegion: string; count: number; sumRemoteSec: number; sampleTo?: string };
      const grouped: Map<string, Agg> = new Map();
      for (const [fromName, sys] of systems.entries()) {
        const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
        for (const n of neighbors) {
          if (systems.has(n)) continue;
          const toRegion = nameToRegion[n];
          if (!toRegion || toRegion === regionName) continue;
          const remote = regionSystemsCache.get(toRegion)?.get(n);
          const gkey = `${fromName}__${toRegion}`;
          const agg = grouped.get(gkey) || { from: fromName, toRegion, count: 0, sumRemoteSec: 0, sampleTo: n };
          agg.count += 1;
          if (remote) agg.sumRemoteSec += (remote.security || 0);
          grouped.set(gkey, agg);
        }
      }
      const out: OffIndicator[] = [];
      for (const [, agg] of grouped) {
        if (agg.count === 0) continue;
        // Angle: point away from existing connections = meanNeighborAngle + PI
        let angle = meanNeighborAngle[agg.from];
        if (angle === undefined) {
          // fallback: away from region centroid
          // compute centroid of current region nodes
          let cx = 0, cy = 0, c = 0;
          systems.forEach(s => { cx += s.x; cy += s.y; c++; });
          if (c > 0) { cx /= c; cy /= c; }
          const sys = systems.get(agg.from)!;
          angle = Math.atan2(sys.y - cy, sys.x - cx);
        }
        angle = angle + Math.PI;

        // Color from avg of local system sec and avg remote sec; local from this system
        const localSec = (systems.get(agg.from)?.security || 0);
        const remoteAvg = agg.count > 0 ? (agg.sumRemoteSec / agg.count) : 0;
        const color = getSecurityColor((localSec + remoteAvg) / 2);
        out.push({ from: agg.from, toRegion: agg.toRegion, count: agg.count, color, angle, sampleTo: agg.sampleTo });
      }
      setOffRegionIndicators(out);
    };
    computeOffRegion();
  }, [systems, regionName, meanNeighborAngle]);

  useEffect(() => {
    if (isWormholeRegion) {
      loadWormholeSystems().then(wormholeSystems => {
        setSystems(wormholeSystems);
      });
    }
  }, [isWormholeRegion]);

  const ensureAnyLoggedIn = async () => {
    try {
      const list = await ListCharacters();
      if (Array.isArray(list) && list.length > 0) return true;
      await StartESILogin();
      toast({ title: 'EVE Login', description: 'Complete login in your browser, then retry.' });
      return false;
    } catch (e: any) {
      await StartESILogin();
      toast({ title: 'EVE Login', description: 'Complete login in your browser, then retry.', variant: 'destructive' });
      return false;
    }
  };

  const handleSystemClick = async (systemName: string) => {
    if (viaMode) {
      setViaQueue(prev => {
        // toggle behavior: add if missing, remove if present
        if (prev.includes(systemName)) {
          const next = prev.filter(n => n !== systemName);
          toast({ title: 'Waypoint removed', description: systemName });
          return next;
        }
        const next = [...prev, systemName];
        toast({ title: 'Waypoint queued', description: systemName });
        return next;
      });
      console.log('VIA waypoint toggle:', systemName);
      return;
    }
    if (focusSystem === systemName) return;
    navigate(`/regions/${regionName}/${systemName}`);
  };

  const handleSystemDoubleClick = async (e: React.MouseEvent, position: Position) => {
    if (!isWormholeRegion) return;
    e.stopPropagation();

    // Create a new system at the clicked position
    const newSystemName = `WH-${Date.now()}`;
    const newSystem: System = {
      solarSystemName: newSystemName,
      x: position.x,
      y: position.y,
      connectedSystems: ''
    }
    const dbSystem = await saveWormholeSystem(newSystem);
    setSystems(new Map(systems).set(newSystemName, dbSystem));
  };

  const handleMapDoubleClick = (e: React.MouseEvent) => {
    if (!isWormholeRegion || !svgRef.current) return;
    e.stopPropagation();

    const point = svgRef.current.createSVGPoint();
    point.x = e.clientX;
    point.y = e.clientY;

    // Convert to SVG coordinates
    const svgPoint = point.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
    const x = svgPoint.x;
    const y = svgPoint.y;

    handleSystemDoubleClick(e, { x, y });
  };

  const handleNodeDragStart = (e: React.MouseEvent, nodeId: string) => {
    if (!isWormholeRegion) return;
    e.stopPropagation();
    setDraggingNode(nodeId);

    const point = svgRef.current.createSVGPoint();
    point.x = e.clientX;
    point.y = e.clientY;

    // Convert to SVG coordinates
    const svgPoint = point.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
    const x = svgPoint.x;
    const y = svgPoint.y;

    setTempConnection({
      from: { x: systems.get(nodeId)!.x, y: systems.get(nodeId)!.y },
      to: { x, y }
    });
  };

  const handleSvgMouseMove = (e: React.MouseEvent) => {
    if (!draggingNode || !isWormholeRegion || !svgRef.current) return;
    e.stopPropagation();

    // Create a point in screen coordinates
    const point = svgRef.current.createSVGPoint();
    point.x = e.clientX;
    point.y = e.clientY;

    // Convert to SVG coordinates
    const svgPoint = point.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
    const x = svgPoint.x;
    const y = svgPoint.y;

    // Check for nearby systems to snap to
    var targetSystem: System | null = null;
    systems.forEach(system => {
      if (system.solarSystemName === draggingNode) return;
      const dx = system.x - x;
      const dy = system.y - y;
      const distance = Math.sqrt(dx * dx + dy * dy);
      if (distance < DRAG_SNAP_DISTANCE) {
        targetSystem = system;
      }
    });

    // Update temp connection with either snapped position or exact mouse position
    setTempConnection(prev => prev ? {
      from: prev.from,
      to: targetSystem ? { x: targetSystem.x, y: targetSystem.y } : { x, y }
    } : null);

    if (targetSystem)
      handleNodeDragEnd(e, targetSystem.solarSystemName);
  };

  const handleSvgMouseUp = (e: React.MouseEvent) => {
    if (!draggingNode || !isWormholeRegion) return;
    e.stopPropagation();
    handleNodeDragEnd(e);
  };

  const handleNodeDragEnd = (e: React.MouseEvent, targetNodeId?: string) => {
    if (!draggingNode || !isWormholeRegion) return;
    e.stopPropagation();

    if (targetNodeId && targetNodeId !== draggingNode) {
      // Check if connection already exists
      const connectionKey = [draggingNode, targetNodeId].sort().join('-');
      const connectionExists = connections.has(connectionKey);

      if (!connectionExists) {
        const systemFrom = systems.get(draggingNode)!;
        const systemTo = systems.get(targetNodeId)!;

        const systemFromConnectedSystems = systemFrom.connectedSystems.split(",").filter(system => system != "");
        systemFromConnectedSystems.push(targetNodeId);
        systemFrom.connectedSystems = systemFromConnectedSystems.join(",");

        const systemToConnectedSystems = systemTo.connectedSystems.split(",").filter(system => system != "");
        systemToConnectedSystems.push(draggingNode);
        systemTo.connectedSystems = systemToConnectedSystems.join(",");

        systems.set(draggingNode, systemFrom);
        systems.set(targetNodeId, systemTo);
        setSystems(new Map(systems));

        // We will just trust that they got saved
        saveWormholeSystem(systemFrom);
        saveWormholeSystem(systemTo);
      }
    }

    setDraggingNode(null);
    setTempConnection(null);
  };

  // Helper: convert client to SVG coords
  const clientToSvg = (clientX: number, clientY: number) => {
    if (!svgRef.current) return { x: 0, y: 0 };
    const pt = svgRef.current.createSVGPoint();
    pt.x = clientX;
    pt.y = clientY;
    const svgPoint = pt.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
    return { x: svgPoint.x, y: svgPoint.y };
  };

  // Helper: find nearest system name to SVG point
  const findNearestSystem = (svgX: number, svgY: number): string | null => {
    if (systems.size === 0) return null;
    let nearestName: string | null = null;
    let nearestDist2 = Number.POSITIVE_INFINITY;
    systems.forEach((_sys, name) => {
      const pos = positions[name];
      if (!pos) return;
      const dx = pos.x - svgX;
      const dy = pos.y - svgY;
      const d2 = dx * dx + dy * dy;
      if (d2 < nearestDist2) {
        nearestDist2 = d2;
        nearestName = name;
      }
    });
    return nearestName;
  };

  // Helper functions to get statistics for a system
  const getSystemJumps = (systemName: string): number | undefined => {
    if (!jumpsData || !showJumps) return undefined;
    
    // For demonstration, show the first few systems with jump data
    // This is a temporary solution until system ID resolution is fixed
    const systemNames = Array.from(systems.keys());
    const systemIndex = systemNames.indexOf(systemName);
    
    if (systemIndex >= 0 && systemIndex < jumpsData.length) {
      const jumps = jumpsData[systemIndex].ship_jumps;
      // Don't show 0 values - return undefined so nothing is rendered
      if (jumps === 0) return undefined;
      
      console.log(`🚀 Found ${jumps} jumps for ${systemName} (using index ${systemIndex})`);
      return jumps;
    }
    
    return undefined;
  };

  const getSystemKills = (systemName: string): number | undefined => {
    if (!killsData || !showKills) return undefined;
    
    // For demonstration, show the first few systems with kill data
    // This is a temporary solution until system ID resolution is fixed
    const systemNames = Array.from(systems.keys());
    const systemIndex = systemNames.indexOf(systemName);
    
    if (systemIndex >= 0 && systemIndex < killsData.length) {
      const kills = killsData[systemIndex].ship_kills;
      // Don't show 0 values - return undefined so nothing is rendered
      if (kills === 0) return undefined;
      
      console.log(`⚔️ Found ${kills} kills for ${systemName} (using index ${systemIndex})`);
      return kills;
    }
    
    return undefined;
  };

  // Commit shift selection: toggle all systems within radius
  const commitShiftSelection = useCallback(() => {
    if (!shiftCenter || shiftRadius <= 0) return;
    const within: string[] = [];
    Object.keys(positions).forEach(name => {
      const pos = positions[name];
      if (!pos) return;
      const dx = pos.x - shiftCenter.x;
      const dy = pos.y - shiftCenter.y;
      const dist = Math.sqrt(dx * dx + dy * dy);
      if (dist <= shiftRadius) within.push(name);
    });
    if (within.length === 0) return;
    setViaQueue(prev => {
      const prevSet = new Set(prev);
      const toToggle = new Set(within);
      // remove toggled ones that were present
      const kept = prev.filter(n => !toToggle.has(n));
      // add new ones (those within but not previously present), preserve within order
      const additions = within.filter(n => !prevSet.has(n));
      const next = kept.concat(additions);
      toast({ title: 'VIA toggled', description: `${within.length} systems` });
      return next;
    });
  }, [positions, shiftCenter, shiftRadius]);

  const clearSelectTimer = () => {
    if (selectTimerRef.current !== null) {
      window.clearTimeout(selectTimerRef.current);
      selectTimerRef.current = null;
    }
  };

  // const PAN_THRESHOLD_PX = 6; // movement before starting pan

  const handleMouseDown = useCallback((e: React.MouseEvent) => {
    if (!svgRef.current) return;

    // If context menu is open, left-click closes it and no selection should happen
    if (contextMenu) {
      if (e.button === 0) setContextMenu(null);
      clearSelectTimer();
      setIsSelecting(false);
      setIsPanning(false);
      setShiftSelecting(false);
      setMode('idle');
      return;
    }

    mouseButtonRef.current = e.button;

    // SHIFT + VIA mode: start circle selection (left button only)
    if (viaMode && e.shiftKey && e.button === 0) {
      e.preventDefault();
      e.stopPropagation();
      const svgPt = clientToSvg(e.clientX, e.clientY);
      setShiftSelecting(true);
      setShiftCenter(svgPt);
      setShiftRadius(0);
      setMode('shiftSelecting');
      // cancel any hold-to-select/pan intents
      setIsSelecting(false);
      setIsPanning(false);
      clearSelectTimer();
      downClientPointRef.current = { x: e.clientX, y: e.clientY };
      return;
    }

    // Only left button initiates selection/panning
    if (e.button !== 0) {
      clearSelectTimer();
      setIsSelecting(false);
      setMode('idle');
      return;
    }

    // record down point (client) and seed pan origin
    const rect = svgRef.current.getBoundingClientRect();
    setLastPanPoint({ x: e.clientX - rect.left, y: e.clientY - rect.top });
    downClientPointRef.current = { x: e.clientX, y: e.clientY };

    // initial indicate nearest system under cursor
    const svgPt = clientToSvg(e.clientX, e.clientY);
    const near = findNearestSystem(svgPt.x, svgPt.y);
    setIndicatedSystem(near);

    // start delayed select mode timer
    setIsSelecting(false);
    setMode('holding');
    clearSelectTimer();
    selectTimerRef.current = window.setTimeout(() => {
      setIsSelecting(true);
      setMode('selecting');
    }, SELECT_HOLD_MS);
  }, [positions, systems, viaMode, contextMenu]);

  const handleMouseMove = useCallback((e: React.MouseEvent) => {
    // if dragging node, delegate
    if (draggingNode) { handleSvgMouseMove(e); return; }

    if (!svgRef.current) return;

    // Shift selection radius update
    if (shiftSelecting && shiftCenter) {
      const svgPt = clientToSvg(e.clientX, e.clientY);
      const dx = svgPt.x - shiftCenter.x;
      const dy = svgPt.y - shiftCenter.y;
      setShiftRadius(Math.sqrt(dx * dx + dy * dy));
      setMode('shiftSelecting');
      return;
    }

    const rect = svgRef.current.getBoundingClientRect();

    if (isPanning) {
      const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top };
      const deltaX = (lastPanPoint.x - currentPoint.x) * (viewBox.width / rect.width);
      const deltaY = (lastPanPoint.y - currentPoint.y) * (viewBox.height / rect.height);
      setViewBox(prev => ({ ...prev, x: prev.x + deltaX, y: prev.y + deltaY }));
      setLastPanPoint(currentPoint);
      setMode('panning');
      return;
    }

    // determine if we should start panning (from holding)
    const down = downClientPointRef.current;
    if (down && !isSelecting) {
      const dx = e.clientX - down.x;
      const dy = e.clientY - down.y;
      const dist2 = dx * dx + dy * dy;
      if (dist2 > PAN_THRESHOLD_PX * PAN_THRESHOLD_PX) {
        // user intends to pan; cancel selection
        clearSelectTimer();
        setIsSelecting(false);
        setIndicatedSystem(null);
        setIsPanning(true);
        setMode('panning');
        // seed pan origin with current
        const currentPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top };
        setLastPanPoint(currentPoint);
        return;
      }
    }

    // selection mode: update indicated nearest system as cursor moves
    if (isSelecting) {
      const svgPt = clientToSvg(e.clientX, e.clientY);
      const near = findNearestSystem(svgPt.x, svgPt.y);
      setIndicatedSystem(near);
      setMode('selecting');
    }
  }, [draggingNode, isPanning, lastPanPoint, viewBox.width, viewBox.height, isSelecting, positions, systems, shiftSelecting, shiftCenter]);

  const handleMouseUp = useCallback((e?: React.MouseEvent) => {
    // if dragging node, delegate
    if (draggingNode) { if (e) handleSvgMouseUp(e); return; }

    // If context menu open, left click should just close it; do not select
    if (contextMenu && mouseButtonRef.current === 0) {
      setContextMenu(null);
      clearSelectTimer();
      setIsPanning(false);
      setIsSelecting(false);
      setIndicatedSystem(null);
      setShiftSelecting(false);
      setShiftCenter(null);
      setShiftRadius(0);
      downClientPointRef.current = null;
      mouseButtonRef.current = null;
      setMode('idle');
      return;
    }

    // Commit shift selection if active (only if left button initiated)
    if (shiftSelecting) {
      if (mouseButtonRef.current === 0) {
        commitShiftSelection();
      }
      setShiftSelecting(false);
      setShiftCenter(null);
      setShiftRadius(0);
      clearSelectTimer();
      setIsPanning(false);
      setIsSelecting(false);
      setIndicatedSystem(null);
      downClientPointRef.current = null;
      mouseButtonRef.current = null;
      setMode('idle');
      return;
    }

    // Ignore non-left button for selection commit
    if (mouseButtonRef.current !== 0) {
      clearSelectTimer();
      setIsPanning(false);
      setIsSelecting(false);
      setIndicatedSystem(null);
      downClientPointRef.current = null;
      mouseButtonRef.current = null;
      setMode('idle');
      return;
    }

    clearSelectTimer();

    if (isPanning) {
      setIsPanning(false);
      mouseButtonRef.current = null;
      setMode('idle');
      return;
    }

    // commit selection if any
    let target = indicatedSystem;
    if (!target && e && svgRef.current) {
      const svgPt = clientToSvg(e.clientX, e.clientY);
      target = findNearestSystem(svgPt.x, svgPt.y);
    }

    if (target) {
      handleSystemClick(target);
    }

    // reset selection state
    setIsSelecting(false);
    setIndicatedSystem(null);
    downClientPointRef.current = null;
    mouseButtonRef.current = null;
    setMode('idle');
  }, [draggingNode, isPanning, indicatedSystem, positions, systems, shiftSelecting, commitShiftSelection, contextMenu]);

  const handleWheel = useCallback((e: React.WheelEvent) => {
    e.preventDefault();
    if (!svgRef.current) return;

    const rect = svgRef.current.getBoundingClientRect();
    const mouseX = e.clientX - rect.left;
    const mouseY = e.clientY - rect.top;

    const scale = e.deltaY < 0 ? 1.1 : 0.9;
    const newWidth = viewBox.width * scale;
    const newHeight = viewBox.height * scale;

    const mouseXInSVG = viewBox.x + (mouseX / rect.width) * viewBox.width;
    const mouseYInSVG = viewBox.y + (mouseY / rect.height) * viewBox.height;

    const newX = mouseXInSVG - (mouseX / rect.width) * newWidth;
    const newY = mouseYInSVG - (mouseY / rect.height) * newHeight;

    setViewBox({
      x: newX,
      y: newY,
      width: newWidth,
      height: newHeight
    });
  }, [viewBox]);

  const handleBackgroundContextMenu = (e: React.MouseEvent) => {
    if (!svgRef.current || systems.size === 0) return;
    e.preventDefault();
    e.stopPropagation();

    // Convert click to SVG coordinates
    const pt = svgRef.current.createSVGPoint();
    pt.x = e.clientX;
    pt.y = e.clientY;
    const svgPoint = pt.matrixTransform(svgRef.current.getScreenCTM()!.inverse());
    const clickX = svgPoint.x;
    const clickY = svgPoint.y;

    // Find nearest system by Euclidean distance in SVG space
    let nearestName: string | null = null;
    let nearestDist2 = Number.POSITIVE_INFINITY;
    systems.forEach((sys, name) => {
      const pos = positions[name];
      if (!pos) return;
      const dx = pos.x - clickX;
      const dy = pos.y - clickY;
      const d2 = dx * dx + dy * dy;
      if (d2 < nearestDist2) {
        nearestDist2 = d2;
        nearestName = name;
      }
    });

    if (nearestName) {
      const sys = systems.get(nearestName)!;
      // Place the menu at the system's on-screen position
      const pt2 = svgRef.current.createSVGPoint();
      pt2.x = positions[nearestName]!.x;
      pt2.y = positions[nearestName]!.y;
      const screenPoint = pt2.matrixTransform(svgRef.current.getScreenCTM()!);
      setContextMenu({ x: screenPoint.x, y: screenPoint.y, system: sys });
    }
  };

  const handleContextMenu = (e: React.MouseEvent, system: System) => {
    e.preventDefault();
    e.stopPropagation();

    const rect = svgRef.current?.getBoundingClientRect();
    if (!rect) return;

    setContextMenu({
      x: e.clientX,
      y: e.clientY,
      system: system
    });
  };

  const handleRenameSystem = (oldName: string, newName: string) => {
    const system = systems.get(oldName);
    if (system) {
      const newSystems = new Map(systems);
      system.solarSystemName = newName;
      newSystems.set(newName, system);
      newSystems.delete(oldName);
      saveWormholeSystem(system);

      // Update all the systems this system connects to
      const connectedSystems = system.connectedSystems.split(',');
      for (const connectedSystem of connectedSystems) {
        const connectedSystemobj = newSystems.get(connectedSystem);
        if (connectedSystemobj) {
          connectedSystemobj.connectedSystems = connectedSystemobj.connectedSystems.replace(oldName, newName);
          newSystems.set(connectedSystem, connectedSystemobj);
          saveWormholeSystem(connectedSystemobj);
        }
      }

      // Update all the systems that connect to this system
      for (const [key, system] of newSystems.entries()) {
        if (system.connectedSystems.includes(oldName)) {
          system.connectedSystems = system.connectedSystems.replace(oldName, newName);
          newSystems.set(key, system);
          saveWormholeSystem(system);
        }
      }

      setSystems(newSystems);
    }

    setContextMenu(null);
  };

  const handleClearConnections = async (system: System) => {
    system.connectedSystems = '';
    for (const [key, isystem] of systems.entries()) {
      if (system.solarSystemName === isystem.solarSystemName) continue;
      if (!isystem.connectedSystems.includes(system.solarSystemName)) continue;
      isystem.connectedSystems = isystem.connectedSystems.split(',').filter(connectedSystem => connectedSystem !== system.solarSystemName).join(',');
      systems.set(key, isystem);
      saveWormholeSystem(isystem);
    }
    setSystems(new Map(systems));
    saveWormholeSystem(system);
    setContextMenu(null);
  };

  const handleDeleteSystem = async (system: System) => {
    try {
      const newSystems = new Map(systems);
      newSystems.delete(system.solarSystemName);
      setSystems(newSystems);
      await deleteWormholeSystem(system);
      setContextMenu(null);
    } catch (error) {
      console.error('Error deleting system:', error);
    }
  };

  const onSetDestination = async (systemName: string, wantVia: boolean) => {
    try {
      if (!(await ensureAnyLoggedIn())) return;
      if (wantVia) {
        setViaDest(systemName);
        setViaQueue([]);
        setViaMode(true);
        console.log('Via mode start, dest:', systemName);
        toast({ title: 'Via mode', description: `Destination ${systemName}. Click systems to add waypoints. Esc to commit.` });
      } else {
        await SetDestinationForAll(systemName, true, false);
        toast({ title: 'Destination set', description: systemName });
      }
    } catch (e: any) {
      console.error('Failed to set destination:', e);
      toast({ title: 'Failed to set destination', description: String(e), variant: 'destructive' });
    }
  };

  // Close context menu when clicking outside
  useEffect(() => {
    const handleClickOutside = () => setContextMenu(null);
    document.addEventListener('click', handleClickOutside);
    return () => document.removeEventListener('click', handleClickOutside);
  }, []);

  useEffect(() => {
    const onWindowMouseUp = () => {
      // if shift selection ongoing, commit on global mouseup as well
      if (shiftSelecting && mouseButtonRef.current === 0) {
        commitShiftSelection();
      }
      clearSelectTimer();
      setIsPanning(false);
      setIsSelecting(false);
      setIndicatedSystem(null);
      setShiftSelecting(false);
      setShiftCenter(null);
      setShiftRadius(0);
      downClientPointRef.current = null;
      mouseButtonRef.current = null;
      setMode('idle');
    };
    window.addEventListener('mouseup', onWindowMouseUp);
    return () => window.removeEventListener('mouseup', onWindowMouseUp);
  }, [shiftSelecting, commitShiftSelection]);

  if (isLoading) {
    return (
      <div className="h-full w-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center">
        <div className="text-white text-xl">Loading {regionName} data...</div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="h-full w-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center">
        <div className="text-center">
          <h1 className="text-4xl font-bold text-white mb-4">Error Loading Region</h1>
          <p className="text-red-400 mb-6">Failed to load data for {regionName}</p>
        </div>
      </div>
    );
  }

  return (
    <div className="w-full h-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 relative">
      <Header
        title={`Region: ${regionName}`}
        breadcrumbs={[
          { label: "Universe", path: "/" },
          { label: regionName }
        ]}
      />
      <svg
        ref={svgRef}
        width="100%"
        height="100%"
        viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`}
        className={`${(mode === 'selecting' || mode === 'shiftSelecting') ? 'cursor-crosshair' : (mode === 'panning' ? 'cursor-grabbing' : 'cursor-grab')}`}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={(e) => handleMouseUp(e)}
        onMouseLeave={handleMouseUp}
        onWheel={handleWheel}
        onDoubleClick={handleMapDoubleClick}
        onContextMenu={handleBackgroundContextMenu}
      >
        <defs>
          <filter id="glow">
            <feGaussianBlur stdDeviation="3" result="coloredBlur" />
            <feMerge>
              <feMergeNode in="coloredBlur" />
              <feMergeNode in="SourceGraphic" />
            </feMerge>
          </filter>
          <marker id="arrowhead" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto" markerUnits="strokeWidth">
            <path d="M0,0 L0,6 L6,3 z" fill="#ffffff" />
          </marker>
        </defs>

        {/* Render all connections */}
        {Array.from(connections.entries()).map(([key, connection]) => (
          <Connection
            key={key}
            from={connection.from}
            to={connection.to}
            color={connection.color}
          />
        ))}

        {/* Render temporary connection while dragging */}
        {tempConnection && (
          <Connection
            from={tempConnection.from}
            to={tempConnection.to}
            color="#f472b6"
          />
        )}

        {/* Shift selection circle (VIA mode) */}
        {shiftSelecting && shiftCenter && (
          <g style={{ pointerEvents: 'none' }}>
            <circle
              cx={shiftCenter.x}
              cy={shiftCenter.y}
              r={Math.max(shiftRadius, 0)}
              fill={SHIFT_SELECT_FILL_COLOR}
              stroke={SHIFT_SELECT_STROKE_COLOR}
              strokeWidth={SHIFT_SELECT_STROKE_WIDTH}
            />
          </g>
        )}

        {/* Render existing systems */}
        {Array.from(systems.entries()).map(([key, system]) => (
          <MapNode
            key={key}
            id={system.solarSystemName}
            name={system.solarSystemName}
            position={positions[system.solarSystemName] || { x: 0, y: 0 }}
            onClick={() => { /* handled at svg-level aimbot commit */ }}
            onDoubleClick={(e) => handleSystemDoubleClick(e, positions[system.solarSystemName])}
            onDragStart={(e) => handleNodeDragStart(e, system.solarSystemName)}
            onDrag={handleSvgMouseMove}
            onDragEnd={handleNodeDragEnd}
            onContextMenu={(e) => handleContextMenu(e, system)}
            type="system"
            security={system.security}
            signatures={system.signatures}
            isDraggable={isWormholeRegion}
            disableNavigate={viaMode}
            jumps={getSystemJumps(system.solarSystemName)}
            kills={getSystemKills(system.solarSystemName)}
            showJumps={showJumps}
            showKills={showKills}
          />
        ))}

        {/* VIA waypoints indicator rings */}
        {viaMode && viaQueue.map((name) => (
          positions[name] ? (
            <g key={`via-${name}`} style={{ pointerEvents: 'none' }}>
              <circle
                cx={positions[name].x}
                cy={positions[name].y}
                r={VIA_WAYPOINT_RING_RADIUS}
                fill="none"
                stroke={VIA_WAYPOINT_RING_COLOR}
                strokeWidth="3"
                opacity="0.9"
                filter="url(#glow)"
              />
            </g>
          ) : null
        ))}

        {/* Indicated (aim) system ring */}
        {indicatedSystem && positions[indicatedSystem] && (
          <g style={{ pointerEvents: 'none' }}>
            <circle
              cx={positions[indicatedSystem].x}
              cy={positions[indicatedSystem].y}
              r={INDICATED_RING_RADIUS}
              fill="none"
              stroke={INDICATED_RING_COLOR}
              strokeWidth="3"
              opacity="0.9"
              filter="url(#glow)"
            >
              <animate attributeName="r" values={INDICATED_RING_ANIM_VALUES} dur={INDICATED_RING_ANIM_DUR} repeatCount="indefinite" />
            </circle>
          </g>
        )}

        {/* Character location markers */}
        {charLocs.map((c, idx) => {
          const pos = positions[c.solar_system_name];
          if (!pos) return null;
          const yoff = -18 - (idx % 3) * 10; // stagger small vertical offsets if multiple in same system
          return (
            <g key={`char-${c.character_id}-${idx}`} transform={`translate(${pos.x}, ${pos.y + yoff})`}>
              <rect x={-2} y={-9} width={Math.max(c.character_name.length * 5, 24)} height={14} rx={3} fill="#0f172a" opacity={0.9} stroke="#00d1ff" strokeWidth={1} />
              <text x={Math.max(c.character_name.length * 5, 24) / 2 - 2} y={2} textAnchor="middle" fontSize={8} fill="#ffffff">{c.character_name}</text>
            </g>
          );
        })}

        {/* Off-region indicators: labeled arrows pointing toward the destination region */}
        {offRegionIndicators.map((ind, idx) => {
          const pos = positions[ind.from];
          if (!pos) return null;
          const len = 26;
          const r0 = 10; // start just outside node
          const dx = Math.cos(ind.angle);
          const dy = Math.sin(ind.angle);
          const x1 = pos.x + dx * r0;
          const y1 = pos.y + dy * r0;
          const x2 = x1 + dx * len;
          const y2 = y1 + dy * len;
          const labelX = x2 + dx * 8;
          const labelY = y2 + dy * 8;
          const label = ind.count > 1 ? `${ind.toRegion} ×${ind.count}` : ind.toRegion;
          return (
            <g key={`offr-${idx}`}>
              <line x1={x1} y1={y1} x2={x2} y2={y2} stroke={ind.color} strokeWidth={2} markerEnd="url(#arrowhead)">
                <title>{label}</title>
              </line>
              <g transform={`translate(${labelX}, ${labelY})`} onClick={(e) => { e.stopPropagation(); navigate(`/regions/${encodeURIComponent(ind.toRegion)}`); }}>
                <rect x={-2} y={-10} width={Math.max(label.length * 5, 24)} height={14} rx={7} fill="#0f172a" opacity={0.85} stroke={ind.color} strokeWidth={1} />
                <text x={Math.max(label.length * 5, 24) / 2 - 2} y={0} textAnchor="middle" fontSize="8" fill="#ffffff">{label}</text>
              </g>
            </g>
          );
        })}

        {/* Highlight focused system */}
        {focusSystem && focusUntil && Date.now() <= focusUntil && positions[focusSystem] && (
          <g style={{ pointerEvents: 'none' }}>
            <circle
              cx={positions[focusSystem].x}
              cy={positions[focusSystem].y}
              r="20"
              fill="none"
              stroke="#a855f7"
              strokeWidth="3"
              opacity="0.9"
              filter="url(#glow)"
            >
              <animate
                attributeName="r"
                values="18;22;18"
                dur="1.5s"
                repeatCount="indefinite"
              />
            </circle>
            <text x={positions[focusSystem].x + 12} y={positions[focusSystem].y - 10} fontSize="10" fill="#ffffff" stroke="#0f172a" strokeWidth="2" paintOrder="stroke">{focusSystem}</text>
          </g>
        )}
      </svg>

      {viaMode && (
        <div className="absolute top-2 right-2 px-2 py-1 rounded bg-emerald-600 text-white text-xs shadow">
          VIA mode: Dest {viaDest} ({viaQueue.length} waypoints). Esc to commit
        </div>
      )}

      {/* Statistics Toggle */}
      <StatisticsToggle
        jumpsEnabled={showJumps}
        killsEnabled={showKills}
        onJumpsToggle={setShowJumps}
        onKillsToggle={setShowKills}
      />

      {/* Context Menu */}
      {contextMenu && (
        <SystemContextMenu
          x={contextMenu.x}
          y={contextMenu.y}
          system={systems.get(contextMenu.system.solarSystemName)!}
          onRename={(newName) => handleRenameSystem(contextMenu.system.solarSystemName, newName)}
          onDelete={handleDeleteSystem}
          onClearConnections={handleClearConnections}
          onSetDestination={(systemName, via) => onSetDestination(systemName, via)}
          onClose={() => setContextMenu(null)}
        />
      )}
    </div>
  );
};
