import { IconEyeClosed, IconEyeOpened, IconMapPin, IconRefresh } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useRef, useState } from 'react';
import { getAMapConfig, isAMapConfigured } from '../../config/appConfig';
import {
  gcj02ToWgs84,
  isValidAMapCoordinate,
  loadAMap,
  wgs84ToGcj02,
  type AMapMap,
  type AMapLabelsLayer,
  type AMapLike,
  type AMapMassMarks,
  type AMapMassPoint,
  type AMapOverlay
} from '../../integrations/amap';
import type { MonitorMapResponse, VehicleRealtimeRow } from '../../api/types';
import { vehicleStatus } from '../domain/monitor';
import type { MonitorViewport } from '../hooks/useMonitorData';

const COLORS = ['#12a46f', '#9aa6b7', '#1677ff', '#f59e0b', '#ef4444'];
const CLUSTER_VISUAL_CACHE_LIMIT = 256;
const DEFAULT_POINT_MOVE_DURATION_MS = 15_000;
const MIN_POINT_MOVE_DURATION_MS = 1_000;
const MAX_POINT_MOVE_DURATION_MS = 120_000;
const MIN_POINT_MOVE_FRAME_MS = 100;
const MAX_POINT_MOVE_FRAME_MS = 220;
const POINT_MOVE_EPSILON = 0.000003;
const MAP_FOLLOW_FRAME_MS = 500;
const MAP_INTERACTION_PAN_DURATION_MS = 650;
const clusterVisualCache = new Map<number, { diameter: number; url: string }>();

function dotDataUrl(color: string) {
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="54" height="54" viewBox="0 0 18 18"><circle cx="9" cy="9" r="6.5" fill="${color}" stroke="white" stroke-width="2.5"/></svg>`;
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}

function clusterVisual(count: number) {
  const cached = clusterVisualCache.get(count);
  if (cached) {
    clusterVisualCache.delete(count);
    clusterVisualCache.set(count, cached);
    return cached;
  }
  const label = count.toLocaleString('en-US');
  const diameter = Math.min(54, 34 + Math.max(0, label.length - 1) * 4);
  const center = diameter / 2;
  const fontSize = label.length >= 6 ? 9 : label.length >= 4 ? 10 : 11;
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${diameter * 3}" height="${diameter * 3}" viewBox="0 0 ${diameter} ${diameter}"><circle cx="${center}" cy="${center}" r="${center - 2}" fill="#1268f3" stroke="white" stroke-width="2.5"/><circle cx="${center}" cy="${center}" r="${center - 5}" fill="none" stroke="rgba(255,255,255,.22)" stroke-width="1"/><text x="${center}" y="${center + fontSize * 0.34}" text-anchor="middle" font-family="Inter,Arial,sans-serif" font-size="${fontSize}" font-weight="800" fill="white">${label}</text></svg>`;
  const visual = { diameter, url: `data:image/svg+xml,${encodeURIComponent(svg)}` };
  if (clusterVisualCache.size >= CLUSTER_VISUAL_CACHE_LIMIT) {
    const oldest = clusterVisualCache.keys().next().value;
    if (oldest !== undefined) clusterVisualCache.delete(oldest);
  }
  clusterVisualCache.set(count, visual);
  return visual;
}

function viewportFromMap(map: AMapMap): MonitorViewport | null {
  const zoom = Math.round(map.getZoom?.() ?? 5);
  const bounds = map.getBounds?.();
  const southWest = bounds?.getSouthWest?.();
  const northEast = bounds?.getNorthEast?.();
  const values = [southWest?.getLng?.(), southWest?.getLat?.(), northEast?.getLng?.(), northEast?.getLat?.()];
  if (values.some((value) => !Number.isFinite(value))) return { zoom, bounds: '' };
  const west = Number(values[0]);
  const south = Number(values[1]);
  const east = Number(values[2]);
  const north = Number(values[3]);
  const wgsCorners = [
    gcj02ToWgs84(west, south),
    gcj02ToWgs84(west, north),
    gcj02ToWgs84(east, south),
    gcj02ToWgs84(east, north)
  ];
  const longitudes = wgsCorners.map(([longitude]) => longitude);
  const latitudes = wgsCorners.map(([, latitude]) => latitude);
  return {
    zoom,
    bounds: [Math.min(...longitudes), Math.min(...latitudes), Math.max(...longitudes), Math.max(...latitudes)]
      .map((value) => value.toFixed(6)).join(',')
  };
}

function styleIndex(vehicle: VehicleRealtimeRow) {
  const status = vehicleStatus(vehicle);
  return statusStyleIndex(status);
}

function statusStyleIndex(status: string) {
  if (status === 'driving') return 2;
  if (status === 'idle') return 0;
  if (status === 'offline') return 1;
  if (status === 'alert') return 4;
  return 3;
}

function escapeHtml(value: string) {
  return value.replace(/[&<>'"]/g, (character) => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;'
  })[character] ?? character);
}

type PlateLabelPoint = {
  vin: string;
  plate: string;
  longitude: number;
  latitude: number;
};

type LabelMarkerEntry = {
  marker: AMapOverlay;
  signature: string;
  position: [number, number];
};

const NO_VEHICLE_POINTS: VehicleRealtimeRow[] = [];

function createPointFingerprint() {
  let left = 0x811c9dc5;
  let right = 0x9e3779b9;
  let fields = 0;
  const add = (value: unknown) => {
    const text = String(value ?? '');
    for (let index = 0; index < text.length; index += 1) {
      const code = text.charCodeAt(index);
      left = Math.imul(left ^ code, 0x01000193);
      right = Math.imul(right ^ code, 0x85ebca6b);
    }
    left = Math.imul(left ^ 31, 0x01000193);
    right = Math.imul(right ^ 127, 0x85ebca6b);
    fields += 1;
  };
  const value = () => `${fields}:${(left >>> 0).toString(36)}:${(right >>> 0).toString(36)}`;
  return { add, value };
}

export function massPointFingerprint(
  mode: MonitorMapResponse['mode'] | undefined,
  clusters: MonitorMapResponse['clusters'] | undefined,
  mapPoints: MonitorMapResponse['points'] | undefined,
  points: VehicleRealtimeRow[]
) {
  const fingerprint = createPointFingerprint();
  fingerprint.add(mode ?? 'vehicles');
  if (mode) {
    const clusterRows = clusters ?? [];
    const pointRows = mapPoints ?? [];
    fingerprint.add(clusterRows.length);
    for (const cluster of clusterRows) {
      fingerprint.add(cluster.id);
      fingerprint.add(cluster.longitude);
      fingerprint.add(cluster.latitude);
      fingerprint.add(cluster.count);
    }
    fingerprint.add(pointRows.length);
    for (const point of pointRows) {
      fingerprint.add(point.vin);
      fingerprint.add(point.longitude);
      fingerprint.add(point.latitude);
      fingerprint.add(point.status);
      fingerprint.add(point.plate || '');
      fingerprint.add(point.reportIntervalMs ?? '');
    }
  } else {
    fingerprint.add(points.length);
    for (const point of points) {
      fingerprint.add(point.vin);
      fingerprint.add(point.longitude);
      fingerprint.add(point.latitude);
      fingerprint.add(vehicleStatus(point));
      fingerprint.add(point.plate || '');
      fingerprint.add(point.reportIntervalMs ?? '');
    }
  }
  return fingerprint.value();
}

function labelMarkerSignature(point: PlateLabelPoint, selected: boolean, placement?: { direction: 'left' | 'right'; textOffset: [number, number] }) {
  return `${point.plate || point.vin}:${selected ? 1 : 0}:${placement?.direction ?? 'right'}:${placement?.textOffset.join(',') ?? '8,0'}`;
}

function interpolateMassPointsFrom(previousByID: Map<string, AMapMassPoint>, target: AMapMassPoint[], progressFor: (id: string) => number, eligibleIDs?: Set<string>) {
  return target.map((point) => {
    if (eligibleIDs && !eligibleIDs.has(point.id)) return point;
    const start = previousByID.get(point.id);
    if (!start || start.lnglat[0] === point.lnglat[0] && start.lnglat[1] === point.lnglat[1]) return point;
    const progress = Math.min(1, Math.max(0, progressFor(point.id)));
    return {
      ...point,
      lnglat: [
        start.lnglat[0] + (point.lnglat[0] - start.lnglat[0]) * progress,
        start.lnglat[1] + (point.lnglat[1] - start.lnglat[1]) * progress
      ] as [number, number]
    };
  });
}

export function interpolateMassPoints(previous: AMapMassPoint[], target: AMapMassPoint[], progress: number) {
  return interpolateMassPointsFrom(new Map(previous.map((point) => [point.id, point])), target, () => progress);
}

export function pointMoveDurationMs(reportIntervalMs?: number) {
  if (!Number.isFinite(reportIntervalMs) || Number(reportIntervalMs) <= 0) return DEFAULT_POINT_MOVE_DURATION_MS;
  return Math.min(MAX_POINT_MOVE_DURATION_MS, Math.max(MIN_POINT_MOVE_DURATION_MS, Math.round(Number(reportIntervalMs))));
}

export type MutablePointMotion = {
  point: AMapMassPoint;
  start: [number, number];
  target: [number, number];
  durationMs: number;
};

export function advancePointMotions(motions: MutablePointMotion[], elapsedMs: number) {
  for (const motion of motions) {
    const progress = Math.min(1, Math.max(0, elapsedMs / motion.durationMs));
    motion.point.lnglat[0] = motion.start[0] + (motion.target[0] - motion.start[0]) * progress;
    motion.point.lnglat[1] = motion.start[1] + (motion.target[1] - motion.start[1]) * progress;
  }
}

export function pointMoveFrameMs(totalPoints: number, movingPoints: number) {
  if (totalPoints <= 2) return MIN_POINT_MOVE_FRAME_MS;
  const pressure = Math.max(totalPoints / 800, movingPoints / 120);
  return Math.round(Math.min(MAX_POINT_MOVE_FRAME_MS, MIN_POINT_MOVE_FRAME_MS + pressure * 120));
}

function movingPointIDs(previous: AMapMassPoint[], target: AMapMassPoint[]) {
  const previousByID = new Map(previous.map((point) => [point.id, point.lnglat]));
  const moving = new Set<string>();
  for (const point of target) {
    const start = previousByID.get(point.id);
    if (start && (Math.abs(start[0] - point.lnglat[0]) >= POINT_MOVE_EPSILON || Math.abs(start[1] - point.lnglat[1]) >= POINT_MOVE_EPSILON)) moving.add(point.id);
  }
  return moving;
}

function selectionStatus(target: PlateLabelPoint & { status?: string; online?: boolean; speedKmh?: number }) {
  const status = target.status || vehicleStatus(target as VehicleRealtimeRow);
  return ['driving', 'idle', 'offline', 'alert'].includes(status) ? status : 'unknown';
}

function densePlatePlacements(points: PlateLabelPoint[]) {
  const buckets = new Map<string, PlateLabelPoint[]>();
  for (const point of points) {
    const key = `${Math.round(point.longitude / 0.00018)}:${Math.round(point.latitude / 0.00008)}`;
    const bucket = buckets.get(key);
    if (bucket) bucket.push(point);
    else buckets.set(key, [point]);
  }
  const placements = new Map<string, {
    direction: 'left' | 'right';
    textOffset: [number, number];
  }>();
  for (const bucket of buckets.values()) {
    bucket.sort((left, right) => left.vin.localeCompare(right.vin));
    bucket.forEach((point, index) => {
      const column = Math.floor(index / 7);
      const row = index % 7;
      const rowsInColumn = Math.min(7, bucket.length - column * 7);
      const direction = column % 2 === 0 ? 'right' : 'left';
      placements.set(point.vin, {
        direction,
        textOffset: [8 + Math.floor(column / 2) * 78, (row - (rowsInColumn - 1) / 2) * 23]
      });
    });
  }
  return placements;
}

export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelectVin, onViewportChange }: {
  vehicles: VehicleRealtimeRow[];
  selectedVin?: string;
  onSelect: (vehicle: VehicleRealtimeRow) => void;
  monitorMap?: MonitorMapResponse;
  onSelectVin?: (vin: string) => void;
  onViewportChange?: (viewport: MonitorViewport) => void;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const mapRef = useRef<AMapMap | null>(null);
  const amapRef = useRef<AMapLike | null>(null);
  const massRef = useRef<AMapMassMarks | null>(null);
  const labelsRef = useRef<AMapLabelsLayer | null>(null);
  const denseLabelsRef = useRef<AMapLabelsLayer | null>(null);
  const selectionRef = useRef<AMapOverlay | null>(null);
  const labelMarkersRef = useRef(new Map<string, LabelMarkerEntry>());
  const labelLayerModeRef = useRef<'normal' | 'dense' | ''>('');
  const labelLayerStateRef = useRef('');
  const massDataSignatureRef = useRef('__unset__');
  const massStyleSignatureRef = useRef('__unset__');
  const renderedMassDataRef = useRef<AMapMassPoint[]>([]);
  const pointAnimationFrameRef = useRef<number | undefined>(undefined);
  const pointAnimationTimerRef = useRef<number | undefined>(undefined);
  const movingPointIDsRef = useRef(new Set<string>());
  const onSelectRef = useRef(onSelect);
  const onSelectVinRef = useRef(onSelectVin);
  const onViewportChangeRef = useRef(onViewportChange);
  const vehiclesRef = useRef(new Map<string, VehicleRealtimeRow>());
  const clustersRef = useRef(new Map<string, { longitude: number; latitude: number }>());
  const viewportTimerRef = useRef<number | undefined>(undefined);
  const selectionKeyRef = useRef('');
  const selectionPositionRef = useRef('');
  const centeredVinRef = useRef('');
  const followSelectedRef = useRef(true);
  const selectedVinRef = useRef(selectedVin);
  const [state, setState] = useState<'loading' | 'ready' | 'fallback' | 'error'>('loading');
  const [loadAttempt, setLoadAttempt] = useState(0);
  const [showLabels, setShowLabels] = useState(true);
  const [followSelected, setFollowSelected] = useState(true);
  const [mapZoom, setMapZoom] = useState(5);
  const points = useMemo(() => vehicles.filter((vehicle) => isValidAMapCoordinate(vehicle.longitude, vehicle.latitude)), [vehicles]);
  const monitorMode = monitorMap?.mode;
  const monitorPoints = monitorMap?.points;
  const monitorClusters = monitorMap?.clusters;
  const fallbackPoints = monitorMode ? NO_VEHICLE_POINTS : points;
  const selectedTarget = useMemo(() => selectedVin
    ? monitorPoints?.find((item) => item.vin === selectedVin) ?? points.find((item) => item.vin === selectedVin)
    : undefined, [monitorPoints, points, selectedVin]);
  const renderedPointCount = monitorPoints?.length ?? fallbackPoints.length;
  const renderedClusterCount = monitorClusters?.length ?? 0;
  const mapComposition = monitorMap && renderedClusterCount > 0
    ? `${renderedClusterCount} 个聚合 · ${renderedPointCount} 个车辆点 · ${monitorMap.total} 辆`
    : `${renderedPointCount} 个有效点位`;
  const initialSelectionRef = useRef(points.find((vehicle) => vehicle.vin === selectedVin));

  useEffect(() => {
    vehiclesRef.current = new Map(points.map((vehicle) => [vehicle.vin, vehicle]));
  }, [points, state]);

  useEffect(() => {
    onSelectRef.current = onSelect;
    onSelectVinRef.current = onSelectVin;
    onViewportChangeRef.current = onViewportChange;
    selectedVinRef.current = selectedVin;
  }, [onSelect, onSelectVin, onViewportChange, selectedVin]);

  useEffect(() => {
    if (!containerRef.current || !isAMapConfigured(getAMapConfig())) {
      setState('fallback');
      return;
    }
    let cancelled = false;
    let resizeTimer: number | undefined;
    let resizeObserver: ResizeObserver | undefined;
    let mapInstance: AMapMap | undefined;
    let massInstance: AMapMassMarks | undefined;
    let selectMassPoint: ((event: { data: AMapMassPoint }) => void) | undefined;
    let notifyViewport: (() => void) | undefined;
    let stopFollowing: (() => void) | undefined;
    setState('loading');
    loadAMap(['AMap.Scale', 'AMap.ToolBar']).then((AMap) => {
      if (cancelled || !containerRef.current) return;
      const initialSelection = initialSelectionRef.current;
      const initialCenter = initialSelection
        ? wgs84ToGcj02(initialSelection.longitude, initialSelection.latitude)
        : wgs84ToGcj02(105.4, 35.9);
      const map = new AMap.Map(containerRef.current, {
        zoom: initialSelection ? 13 : 5,
        center: initialCenter,
        viewMode: '2D',
        mapStyle: 'amap://styles/whitesmoke',
        showLabel: true,
        resizeEnable: true
      });
      mapInstance = map;
      map.addControl(new AMap.Scale());
      if (AMap.ToolBar) map.addControl(new AMap.ToolBar({ position: { right: '18px', bottom: '76px' } }));
      const styles = [
        ...COLORS.map((color) => ({ url: dotDataUrl(color), anchor: new AMap.Pixel(9, 9), size: new AMap.Size(18, 18) }))
      ];
      const mass = new AMap.MassMarks([], { opacity: 0.96, zIndex: 120, cursor: 'pointer', style: styles, zooms: [3, 20] });
      massInstance = mass;
      selectMassPoint = (event: { data: AMapMassPoint }) => {
        const cluster = clustersRef.current.get(event.data.id);
        if (cluster) {
          map.setZoomAndCenter?.(Math.min(20, (map.getZoom?.() ?? 5) + 2), wgs84ToGcj02(cluster.longitude, cluster.latitude));
          return;
        }
        if (onSelectVinRef.current) {
          onSelectVinRef.current(event.data.id);
          return;
        }
        const vehicle = vehiclesRef.current.get(event.data.id);
        if (vehicle) onSelectRef.current(vehicle);
      };
      mass.on('click', selectMassPoint);
      mass.setMap(map);
      const labels = AMap.LabelsLayer ? new AMap.LabelsLayer({ zooms: [11, 18.99], zIndex: 110, collision: true, allowCollision: false }) : null;
      const denseLabels = AMap.LabelsLayer ? new AMap.LabelsLayer({ zooms: [19, 20], zIndex: 110, collision: false, allowCollision: true }) : null;
      labels?.setMap(map);
      notifyViewport = () => {
        setMapZoom(map.getZoom?.() ?? 5);
        window.clearTimeout(viewportTimerRef.current);
        viewportTimerRef.current = window.setTimeout(() => {
          const viewport = viewportFromMap(map);
          if (viewport) onViewportChangeRef.current?.(viewport);
        }, 300);
      };
      map.on?.('moveend', notifyViewport);
      map.on?.('zoomend', notifyViewport);
      stopFollowing = () => {
        if (!centeredVinRef.current) return;
        followSelectedRef.current = false;
        setFollowSelected(false);
      };
      map.on?.('dragstart', stopFollowing);
      mapRef.current = map;
      amapRef.current = AMap;
      massRef.current = mass;
      labelsRef.current = labels;
      denseLabelsRef.current = denseLabels;
      if (typeof ResizeObserver !== 'undefined') {
        resizeObserver = new ResizeObserver(() => {
          window.clearTimeout(resizeTimer);
          resizeTimer = window.setTimeout(() => map.resize?.(), 80);
        });
        resizeObserver.observe(containerRef.current);
      }
      setState('ready');
      notifyViewport();
    }).catch(() => {
      if (!cancelled) setState('error');
    });
    return () => {
      cancelled = true;
      resizeObserver?.disconnect();
      window.clearTimeout(resizeTimer);
      window.clearTimeout(viewportTimerRef.current);
      if (pointAnimationFrameRef.current !== undefined) window.cancelAnimationFrame(pointAnimationFrameRef.current);
      window.clearTimeout(pointAnimationTimerRef.current);
      pointAnimationFrameRef.current = undefined;
      pointAnimationTimerRef.current = undefined;
      movingPointIDsRef.current.clear();
      if (selectMassPoint) massInstance?.off?.('click', selectMassPoint);
      if (notifyViewport) {
        mapInstance?.off?.('moveend', notifyViewport);
        mapInstance?.off?.('zoomend', notifyViewport);
      }
      if (stopFollowing) mapInstance?.off?.('dragstart', stopFollowing);
      massRef.current?.setMap(null);
      labelsRef.current?.clear();
      denseLabelsRef.current?.clear();
      labelsRef.current?.setMap(null);
      denseLabelsRef.current?.setMap(null);
      selectionRef.current?.setMap?.(null);
      mapRef.current?.destroy();
      massRef.current = null;
      labelsRef.current = null;
      denseLabelsRef.current = null;
      selectionRef.current = null;
      labelMarkersRef.current.clear();
      labelLayerModeRef.current = '';
      labelLayerStateRef.current = '';
      massDataSignatureRef.current = '__unset__';
      massStyleSignatureRef.current = '__unset__';
      renderedMassDataRef.current = [];
      amapRef.current = null;
      mapRef.current = null;
    };
  }, [loadAttempt]);

  useEffect(() => {
    const mass = massRef.current;
    const AMap = amapRef.current;
    if (!mass || !AMap) return;
    const clusters = monitorClusters ?? [];
    const mapPoints = monitorPoints ?? [];
    clustersRef.current = new Map(clusters.map((cluster) => [cluster.id, cluster]));
    const clusterCounts = [...new Set(clusters.map((cluster) => cluster.count))].sort((a, b) => a - b);
    const styleSignature = clusterCounts.join(',');
    if (massStyleSignatureRef.current !== styleSignature) {
      const baseStyles = COLORS.map((color) => ({ url: dotDataUrl(color), anchor: new AMap.Pixel(9, 9), size: new AMap.Size(18, 18) }));
      const clusterStyles = clusterCounts.map((count) => {
        const visual = clusterVisual(count);
        return { url: visual.url, anchor: new AMap.Pixel(visual.diameter / 2, visual.diameter / 2), size: new AMap.Size(visual.diameter, visual.diameter) };
      });
      mass.setStyle?.([...baseStyles, ...clusterStyles]);
      massStyleSignatureRef.current = styleSignature;
    }
    const dataSignature = massPointFingerprint(monitorMode, monitorClusters, monitorPoints, fallbackPoints);
    if (massDataSignatureRef.current === dataSignature) return;
    const clusterStyleIndexes = new Map(clusterCounts.map((count, index) => [count, COLORS.length + index]));
    const data: AMapMassPoint[] = monitorMode ? [
      ...clusters.map((cluster) => ({ lnglat: wgs84ToGcj02(cluster.longitude, cluster.latitude), style: clusterStyleIndexes.get(cluster.count) ?? COLORS.length, id: cluster.id, label: `${cluster.count} 辆` })),
      ...mapPoints.map((point) => ({ lnglat: wgs84ToGcj02(point.longitude, point.latitude), style: point.status === 'driving' ? 2 : point.status === 'idle' ? 0 : point.status === 'offline' ? 1 : 3, id: point.vin, label: point.plate || point.vin }))
    ] : fallbackPoints.map((vehicle) => ({
      lnglat: wgs84ToGcj02(vehicle.longitude, vehicle.latitude), style: styleIndex(vehicle), id: vehicle.vin, label: vehicle.plate || vehicle.vin
    }));
    const moveDurationByID = new Map((monitorMode ? mapPoints : fallbackPoints)
      .map((point) => [point.vin, pointMoveDurationMs(point.reportIntervalMs)]));
    massDataSignatureRef.current = dataSignature;
    if (pointAnimationFrameRef.current !== undefined) window.cancelAnimationFrame(pointAnimationFrameRef.current);
    window.clearTimeout(pointAnimationTimerRef.current);
    pointAnimationFrameRef.current = undefined;
    pointAnimationTimerRef.current = undefined;
    const previousData = renderedMassDataRef.current;
    const previousDataByID = new Map(previousData.map((point) => [point.id, point]));
    const vehiclePointIDs = new Set(monitorMode ? mapPoints.map((point) => point.vin) : fallbackPoints.map((point) => point.vin));
    const movingIDs = new Set([...movingPointIDs(previousData, data)].filter((id) => vehiclePointIDs.has(id)));
    movingPointIDsRef.current = movingIDs;
    if (!movingIDs.size || previousData.length === 0 || typeof window.requestAnimationFrame !== 'function' || document.visibilityState === 'hidden') {
      mass.setData(data);
      renderedMassDataRef.current = data;
      movingPointIDsRef.current.clear();
      return;
    }
    const dataByID = new Map(data.map((point) => [point.id, point]));
    const motions: MutablePointMotion[] = [];
    for (const id of movingIDs) {
      const point = dataByID.get(id);
      const previous = previousDataByID.get(id);
      if (!point || !previous) continue;
      const target: [number, number] = [point.lnglat[0], point.lnglat[1]];
      point.lnglat = [previous.lnglat[0], previous.lnglat[1]];
      motions.push({ point, start: [previous.lnglat[0], previous.lnglat[1]], target, durationMs: moveDurationByID.get(id) ?? DEFAULT_POINT_MOVE_DURATION_MS });
    }
    let startedAt: number | undefined;
    const longestMoveDuration = Math.max(...[...movingIDs].map((id) => moveDurationByID.get(id) ?? DEFAULT_POINT_MOVE_DURATION_MS));
    const frameMs = pointMoveFrameMs(data.length, motions.length);
    let lastFollowAt = 0;
    let selectedFollowComplete = false;
    const paint = (elapsed: number) => {
      advancePointMotions(motions, elapsed);
      mass.setData(data);
      renderedMassDataRef.current = data;
      for (const motion of motions) {
        const label = labelMarkersRef.current.get(motion.point.id);
        label?.marker.setPosition?.(motion.point.lnglat);
        if (label) label.position = motion.point.lnglat;
        if (selectedVinRef.current === motion.point.id) {
          selectionRef.current?.setPosition?.(motion.point.lnglat);
          const motionComplete = elapsed >= motion.durationMs;
          if (followSelectedRef.current && !selectedFollowComplete && (elapsed - lastFollowAt >= MAP_FOLLOW_FRAME_MS || motionComplete)) {
            mapRef.current?.setCenter?.(motion.point.lnglat);
            lastFollowAt = elapsed;
            selectedFollowComplete = motionComplete;
          }
        }
      }
    };
    const schedule = () => {
      pointAnimationTimerRef.current = window.setTimeout(() => {
        pointAnimationTimerRef.current = undefined;
        pointAnimationFrameRef.current = window.requestAnimationFrame(animate);
      }, frameMs);
    };
    const animate = (timestamp: number) => {
      pointAnimationFrameRef.current = undefined;
      startedAt ??= timestamp;
      const elapsed = timestamp - startedAt;
      const complete = elapsed >= longestMoveDuration || document.visibilityState === 'hidden';
      paint(complete ? longestMoveDuration : elapsed);
      if (!complete) schedule();
      else {
        movingPointIDsRef.current.clear();
      }
    };
    pointAnimationFrameRef.current = window.requestAnimationFrame(animate);
  }, [fallbackPoints, monitorClusters, monitorMode, monitorPoints, state]);

  useEffect(() => {
    const labels = labelsRef.current;
    const denseLabels = denseLabelsRef.current;
    const AMap = amapRef.current;
    const map = mapRef.current;
    if (!labels || !denseLabels || !AMap?.LabelMarker || !map) return;
    const mapLabelPoints = (monitorPoints
      ? monitorPoints
      : fallbackPoints.map((vehicle) => ({ ...vehicle, status: vehicleStatus(vehicle) })));
    const allLabelPoints = selectedTarget && !mapLabelPoints.some((point) => point.vin === selectedTarget.vin)
      ? [...mapLabelPoints, selectedTarget]
      : mapLabelPoints;
    const labelPoints = showLabels ? allLabelPoints : allLabelPoints.filter((point) => point.vin === selectedVin);
    const showEveryPlate = mapZoom >= 19;
    const layerMode = showEveryPlate ? 'dense' : 'normal';
    const activeLabels = showEveryPlate ? denseLabels : labels;
    if (labelLayerModeRef.current && labelLayerModeRef.current !== layerMode) {
      (labelLayerModeRef.current === 'dense' ? denseLabels : labels).clear();
      labelMarkersRef.current.clear();
    }
    labelLayerModeRef.current = layerMode;
    const layerState = `${layerMode}:${labelPoints.length > 0 ? 'visible' : 'hidden'}`;
    if (labelLayerStateRef.current !== layerState) {
      labels.setMap(layerMode === 'normal' && labelPoints.length ? map : null);
      denseLabels.setMap(layerMode === 'dense' && labelPoints.length ? map : null);
      labelLayerStateRef.current = layerState;
    }
    const densePlacements = showEveryPlate ? densePlatePlacements(labelPoints) : null;
    const nextMarkers = new Map<string, LabelMarkerEntry>();
    const markersToAdd: AMapOverlay[] = [];
    for (const point of labelPoints) {
      const placement = densePlacements?.get(point.vin);
      const signature = labelMarkerSignature(point, point.vin === selectedVin, placement);
      const existing = labelMarkersRef.current.get(point.vin);
      if (existing?.signature === signature) {
        const position = wgs84ToGcj02(point.longitude, point.latitude);
        if (!movingPointIDsRef.current.has(point.vin) && (existing.position[0] !== position[0] || existing.position[1] !== position[1])) {
          existing.marker.setPosition?.(position);
          existing.position = position;
        }
        nextMarkers.set(point.vin, existing);
        continue;
      }
      const position = wgs84ToGcj02(point.longitude, point.latitude);
      const marker = new AMap.LabelMarker!({
        name: point.plate || point.vin,
        position,
        rank: point.vin === selectedVin ? 100 : 1,
        zIndex: point.vin === selectedVin ? 10 : 1,
        text: {
          content: point.plate || point.vin,
          direction: placement?.direction ?? 'right',
          offset: placement?.textOffset ?? [8, 0],
          style: {
            fontSize: 11,
            fontWeight: 700,
            fillColor: '#174d9f',
            strokeColor: 'transparent',
            strokeWidth: 0,
            padding: [5, 9],
            backgroundColor: '#eef5ff',
            borderColor: '#7fb0fa',
            borderWidth: 1,
            borderRadius: 6,
            shadowColor: 'rgba(18, 104, 243, 0.18)',
            shadowBlur: 14,
            shadowOffsetY: 5
          }
        }
      });
      nextMarkers.set(point.vin, { marker, signature, position });
      markersToAdd.push(marker);
    }
    const markersToRemove = [...labelMarkersRef.current.entries()]
      .filter(([vin, entry]) => nextMarkers.get(vin) !== entry)
      .map(([, entry]) => entry.marker);
    if (markersToRemove.length) activeLabels.remove(markersToRemove);
    if (markersToAdd.length) activeLabels.add(markersToAdd);
    labelMarkersRef.current = nextMarkers;
  }, [fallbackPoints, mapZoom, monitorPoints, selectedTarget, selectedVin, showLabels, state]);

  useEffect(() => {
    if (!selectedVin || !mapRef.current || !amapRef.current) {
      selectionRef.current?.setMap?.(null);
      selectionRef.current = null;
      selectionKeyRef.current = '';
      selectionPositionRef.current = '';
      centeredVinRef.current = '';
      followSelectedRef.current = false;
      setFollowSelected(false);
      return;
    }
    const target = selectedTarget;
    if (!target) return;
    const label = escapeHtml(target.plate || target.vin);
    const status = selectionStatus(target);
    const mapPosition = wgs84ToGcj02(target.longitude, target.latitude);
    const selectionKey = `${selectedVin}|${label}|${status}`;
    const positionKey = `${target.longitude.toFixed(6)},${target.latitude.toFixed(6)}`;
    if (centeredVinRef.current !== selectedVin) {
      followSelectedRef.current = true;
      setFollowSelected(true);
      const currentZoom = mapRef.current.getZoom?.() ?? 15;
      if (currentZoom < 15) mapRef.current.setZoomAndCenter?.(15, mapPosition);
      else mapRef.current.panTo?.(mapPosition, MAP_INTERACTION_PAN_DURATION_MS);
      centeredVinRef.current = selectedVin;
    } else if (selectionPositionRef.current && selectionPositionRef.current !== positionKey && followSelectedRef.current && !movingPointIDsRef.current.has(selectedVin)) {
      mapRef.current.panTo?.(mapPosition, MAP_INTERACTION_PAN_DURATION_MS);
    }
    const previousPositionKey = selectionPositionRef.current;
    selectionPositionRef.current = positionKey;
    if (selectionKeyRef.current === selectionKey && selectionRef.current) {
      if (previousPositionKey !== positionKey && !movingPointIDsRef.current.has(selectedVin)) selectionRef.current.setPosition?.(mapPosition);
      return;
    }
    selectionRef.current?.setMap?.(null);
    selectionRef.current = null;
    const marker = new amapRef.current.Marker({
      position: mapPosition,
      offset: new amapRef.current.Pixel(-24, -24),
      zIndex: 300,
      content: `<div class="v2-map-selection-marker is-${status}" aria-label="已选车辆 ${label}"><i></i><i></i><b></b></div>`
    });
    marker.setMap?.(mapRef.current);
    selectionRef.current = marker;
    selectionKeyRef.current = selectionKey;
  }, [selectedTarget, selectedVin, state]);

  const toggleFollow = () => {
    const next = !followSelected;
    followSelectedRef.current = next;
    setFollowSelected(next);
    if (next && selectedTarget && mapRef.current) {
      mapRef.current.panTo?.(wgs84ToGcj02(selectedTarget.longitude, selectedTarget.latitude), MAP_INTERACTION_PAN_DURATION_MS);
    }
  };

  return (
    <div className="v2-fleet-map">
      <div ref={containerRef} className="v2-fleet-map-canvas" aria-label="车辆全局监控地图" />
      <div className="v2-map-controls">
        {selectedVin ? (
          <button
            type="button"
            className={`v2-map-follow-control${followSelected ? ' is-active' : ''}`}
            aria-label="跟随车辆"
            aria-pressed={followSelected}
            title={followSelected ? '车辆移动时保持居中；拖动地图可暂停' : '恢复车辆居中跟随'}
            onClick={toggleFollow}
          >
            <IconMapPin />
            <span><strong>跟随车辆</strong><small>{followSelected ? '实时居中' : '已暂停'}</small></span>
          </button>
        ) : null}
        <button
          type="button"
          className="v2-map-layer-control"
          aria-label="悬浮车牌"
          aria-pressed={showLabels}
          title={monitorMap?.mode === 'clusters' ? '放大地图后显示车辆车牌' : '显示或隐藏车辆悬浮车牌'}
          onClick={() => setShowLabels((current) => !current)}
        >
          {showLabels ? <IconEyeOpened /> : <IconEyeClosed />}
          <span><strong>悬浮车牌</strong><small>{monitorMap?.mode === 'clusters' ? '放大后显示' : '仅明细点'}</small></span>
          <i className={showLabels ? 'is-on' : ''} />
        </button>
      </div>
      {state !== 'ready' ? (
        <div className={`v2-map-state is-${state}`}>
          {state === 'loading' ? <><span className="v2-spinner" />高德地图加载中</> : null}
          {state === 'fallback' ? `地图未配置，当前已载入 ${renderedPointCount} 个有效坐标` : null}
          {state === 'error' ? <><span>地图加载失败，请检查高德 Key、域名白名单和网络</span><button type="button" onClick={() => setLoadAttempt((value) => value + 1)}><IconRefresh />重新加载地图</button></> : null}
        </div>
      ) : null}
      <div className="v2-map-legend" aria-label="车辆状态图例">
        <span><i className="is-driving" />行驶</span>
        <span><i className="is-idle" />静止</span>
        <span><i className="is-offline" />离线</span>
        <span><i className="is-alert" />告警</span>
        <b>{mapComposition}</b>
      </div>
    </div>
  );
}
