"use client";
import { config } from "@/config/config";
import { useProject } from "@/contexts/ProjectContext";
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  useMemo,
  useCallback,
  useRef,
} from "react";
import { Map as OlMap } from "ol";
import View from "ol/View.js";
import "ol/ol.css";
import MapTools from "./MapTools";

// 导入 DeckLayer
import { DeckLayer } from "@utils/layers";
import { toLonLat } from "ol/proj";
import { along, bearing, lineString, length } from "@turf/turf";
import { Deck } from "@deck.gl/core";
import { TextLayer } from "@deck.gl/layers";
import { TripsLayer } from "@deck.gl/geo-layers";
import { CollisionFilterExtension } from "@deck.gl/extensions";
import { ContourLayer } from "deck.gl";
import { isLpsFlowProperty, toM3h } from "@utils/units";
import { usePathname } from "next/navigation";
import {
  cleanupTransientMapResources,
  disposeMapResources,
  markMapResourcePersistent,
} from "./mapLifecycle";
import {
  createOperationalMapResources,
  createOperationalMapSources,
} from "./operationalLayers";
import { getRoundedCurrentTimelineMinutes } from "./Controls/timelineTime";
import { useTimelineTimeConfig } from "./Controls/useTimelineTimeConfig";
import {
  TileFeatureIndex,
  clipLineStringPartsToExtent,
  coordinatesToLonLat,
  lineStringFromFlatCoordinates,
  type TileFeatureInstance,
} from "./tileFeatureIndex";

interface MapComponentProps {
  children?: React.ReactNode;
}

const COMPARE_OPEN_START_MARK = "tjwater:compare-open-start";
const COMPARE_OPEN_READY_MARK = "tjwater:compare-open-ready";
const COMPARE_OPEN_MEASURE = "tjwater:compare-open";

const markCompareOpenStart = () => {
  performance.clearMarks(COMPARE_OPEN_START_MARK);
  performance.clearMarks(COMPARE_OPEN_READY_MARK);
  performance.clearMeasures(COMPARE_OPEN_MEASURE);
  performance.mark(COMPARE_OPEN_START_MARK);
};

const markCompareOpenReady = () => {
  if (performance.getEntriesByName(COMPARE_OPEN_START_MARK).length === 0) {
    return;
  }
  performance.mark(COMPARE_OPEN_READY_MARK);
  performance.measure(
    COMPARE_OPEN_MEASURE,
    COMPARE_OPEN_START_MARK,
    COMPARE_OPEN_READY_MARK,
  );
};

interface DataContextType {
  currentTime?: number; // 当前时间
  setCurrentTime?: React.Dispatch<React.SetStateAction<number>>;
  selectedDate?: Date; // 选择的日期
  schemeName?: string; // 当前方案名称
  setSchemeName?: React.Dispatch<React.SetStateAction<string>>;
  setSelectedDate?: React.Dispatch<React.SetStateAction<Date>>;
  currentJunctionCalData?: any[]; // 当前计算结果
  setCurrentJunctionCalData?: React.Dispatch<React.SetStateAction<any[]>>;
  currentPipeCalData?: any[]; // 当前计算结果
  setCurrentPipeCalData?: React.Dispatch<React.SetStateAction<any[]>>;
  compareJunctionCalData?: any[];
  setCompareJunctionCalData?: React.Dispatch<React.SetStateAction<any[]>>;
  comparePipeCalData?: any[];
  setComparePipeCalData?: React.Dispatch<React.SetStateAction<any[]>>;
  isCompareMode?: boolean;
  setCompareMode?: React.Dispatch<React.SetStateAction<boolean>>;
  toggleCompareMode?: () => void;
  showJunctionText?: boolean; // 是否显示节点文本
  showPipeText?: boolean; // 是否显示管道文本
  showJunctionId?: boolean; // 是否显示节点ID
  showPipeId?: boolean; // 是否显示管道ID
  setShowJunctionTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
  setShowPipeTextLayer?: React.Dispatch<React.SetStateAction<boolean>>;
  setShowJunctionId?: React.Dispatch<React.SetStateAction<boolean>>;
  setShowPipeId?: React.Dispatch<React.SetStateAction<boolean>>;
  showContourLayer?: boolean;
  setShowContourLayer?: React.Dispatch<React.SetStateAction<boolean>>;
  isContourLayerAvailable?: boolean;
  showWaterflowLayer?: boolean;
  setShowWaterflowLayer?: React.Dispatch<React.SetStateAction<boolean>>;
  setContourLayerAvailable?: React.Dispatch<React.SetStateAction<boolean>>;
  isWaterflowLayerAvailable?: boolean;
  setWaterflowLayerAvailable?: React.Dispatch<React.SetStateAction<boolean>>;
  junctionText: string;
  pipeText: string;
  setJunctionText?: React.Dispatch<React.SetStateAction<string>>;
  setPipeText?: React.Dispatch<React.SetStateAction<string>>;
  setContours?: React.Dispatch<React.SetStateAction<any[]>>;
  deckLayer?: DeckLayer;
  compareDeckLayer?: DeckLayer;
  deckLayers?: DeckLayer[];
  compareMap?: OlMap;
  maps?: OlMap[];
  diameterRange?: [number, number];
  elevationRange?: [number, number];
  forceStyleAutoApplyVersion?: number;
  setForceStyleAutoApplyVersion?: React.Dispatch<React.SetStateAction<number>>;
}

// 跨组件传递
const MapContext = createContext<OlMap | undefined>(undefined);
const DataContext = createContext<DataContextType | undefined>(undefined);

// 添加防抖函数
type DebouncedFunction<F extends (...args: any[]) => any> = ((
  ...args: Parameters<F>
) => void) & {
  cancel: () => void;
};

function debounce<F extends (...args: any[]) => any>(
  func: F,
  waitFor: number
): DebouncedFunction<F> {
  let timeout: ReturnType<typeof setTimeout> | null = null;

  const debounced = (...args: Parameters<F>): void => {
    if (timeout !== null) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => func(...args), waitFor);
  };

  debounced.cancel = () => {
    if (timeout !== null) {
      clearTimeout(timeout);
      timeout = null;
    }
  };

  return debounced;
}

const indexCalculationRecords = (records: any[]) =>
  new Map(records.map((record) => [String(record.ID), record]));

const mergeJunctionValues = (
  features: any[],
  records: any[],
  property: string,
) => {
  const recordsById = indexCalculationRecords(records);
  return features.map((feature) => {
    const record = recordsById.get(String(feature.id));
    if (!record) return feature;
    const value = isLpsFlowProperty(property)
      ? toM3h(record.value, "lps")
      : record.value;
    return { ...feature, [property]: value };
  });
};

const mergePipeValues = (
  features: any[],
  records: any[],
  property: string,
) => {
  const recordsById = indexCalculationRecords(records);
  const isFlow = property === "flow";
  return features.map((feature) => {
    const record = recordsById.get(String(feature.id));
    if (!record) return feature;
    const value = isFlow ? toM3h(record.value, "lps") : record.value;
    const reverseFlow = isFlow && record.value < 0;
    return {
      ...feature,
      [property]: isFlow ? Math.abs(value) : value,
      flowFlag: reverseFlow ? -1 : 1,
      path: reverseFlow ? [...feature.path].reverse() : feature.path,
    };
  });
};

const getNumericRange = (values: number[]): [number, number] | undefined => {
  let min = Infinity;
  let max = -Infinity;
  values.forEach((value) => {
    if (!Number.isFinite(value)) return;
    min = Math.min(min, value);
    max = Math.max(max, value);
  });
  return min === Infinity ? undefined : [min, max];
};

export const useMap = () => {
  return useContext(MapContext);
};
export const useData = () => {
  return useContext(DataContext);
};

const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
  const pathname = usePathname();
  const project = useProject();
  const MAP_WORKSPACE = project?.workspace || config.MAP_WORKSPACE;
  const MAP_EXTENT = (project?.extent || config.MAP_EXTENT) as [
    number,
    number,
    number,
    number,
  ];
  const MAP_URL = config.MAP_URL;
  const MAP_VIEW_STORAGE_KEY = `${MAP_WORKSPACE}_map_view`; // 持久化 key
  const { durationMinutes, stepMinutes } = useTimelineTimeConfig();

  const mapRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const compareMapRef = useRef<HTMLDivElement | null>(null);
  const compareCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const deckLayerRef = useRef<DeckLayer | null>(null);
  const compareDeckLayerRef = useRef<DeckLayer | null>(null);
  const isDisposingRef = useRef(false);
  const isCompareDisposingRef = useRef(false);

  const [map, setMap] = useState<OlMap>();
  const [deckLayer, setDeckLayer] = useState<DeckLayer>();
  const [compareMap, setCompareMap] = useState<OlMap>();
  const [compareDeckLayer, setCompareDeckLayer] = useState<DeckLayer>();
  // currentCalData 用于存储当前计算结果
  const [currentTime, setCurrentTime] = useState<number>(
    getRoundedCurrentTimelineMinutes,
  ); // 默认选择当前时间
  // const [selectedDate, setSelectedDate] = useState<Date>(new Date("2025-9-17"));
  const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 默认今天
  const [schemeName, setSchemeName] = useState<string>(""); // 当前方案名称
  // 记录 id、对应属性的计算值
  const [currentJunctionCalData, setCurrentJunctionCalData] = useState<any[]>(
    [],
  );
  const [currentPipeCalData, setCurrentPipeCalData] = useState<any[]>([]);
  const [compareJunctionCalData, setCompareJunctionCalData] = useState<any[]>(
    [],
  );
  const [comparePipeCalData, setComparePipeCalData] = useState<any[]>([]);
  const [isCompareMode, setCompareMode] = useState(false);
  // junctionData 为当前层级和视口内按 ID 去重的节点数据；pipeData 为管道标签数据。
  // pipeFragments 保留当前层级和视口内每个瓦片管道片段，水流动画按 instanceKey 渲染，不能按业务 ID 去重。
  const [junctionData, setJunctionDataState] = useState<any[]>([]);
  const [pipeData, setPipeDataState] = useState<any[]>([]);
  const [pipeFragments, setPipeFragments] = useState<any[]>([]);
  const junctionIndexRef = useRef<TileFeatureIndex | null>(null);
  const pipeIndexRef = useRef<TileFeatureIndex | null>(null);

  const [showJunctionTextLayer, setShowJunctionTextLayer] = useState(false); // 控制节点文本图层显示
  const [showPipeTextLayer, setShowPipeTextLayer] = useState(false); // 控制管道文本图层显示
  const [showJunctionId, setShowJunctionId] = useState(false); // 控制节点ID显示
  const [showPipeId, setShowPipeId] = useState(false); // 控制管道ID显示
  const [showContourLayer, setShowContourLayer] = useState(false); // 控制等高线图层显示
  const [junctionText, setJunctionText] = useState("pressure");
  const [pipeText, setPipeText] = useState("velocity");
  const [contours, setContours] = useState<any[]>([]);
  const [isContourLayerAvailable, setContourLayerAvailable] = useState(false); // 控制等高线图层显示
  const [isWaterflowLayerAvailable, setWaterflowLayerAvailable] =
    useState(false); // 控制等高线图层显示
  const [showWaterflowLayer, setShowWaterflowLayer] = useState(false); // 控制等高线图层显示
  const [currentZoom, setCurrentZoom] = useState(11); // 当前缩放级别

  // 实时合并计算结果到基础地理数据中
  const mergedJunctionData = useMemo(
    () =>
      mergeJunctionValues(junctionData, currentJunctionCalData, junctionText),
    [junctionData, currentJunctionCalData, junctionText],
  );
  const mergedPipeData = useMemo(
    () => mergePipeValues(pipeData, currentPipeCalData, pipeText),
    [pipeData, currentPipeCalData, pipeText],
  );
  const mergedPipeFragments = useMemo(
    () => mergePipeValues(pipeFragments, currentPipeCalData, pipeText),
    [pipeFragments, currentPipeCalData, pipeText],
  );
  const mergedCompareJunctionData = useMemo(
    () =>
      isCompareMode
        ? mergeJunctionValues(junctionData, compareJunctionCalData, junctionText)
        : [],
    [isCompareMode, junctionData, compareJunctionCalData, junctionText],
  );
  const mergedComparePipeData = useMemo(
    () =>
      isCompareMode
        ? mergePipeValues(pipeData, comparePipeCalData, pipeText)
        : [],
    [isCompareMode, pipeData, comparePipeCalData, pipeText],
  );
  const mergedComparePipeFragments = useMemo(
    () =>
      isCompareMode
        ? mergePipeValues(pipeFragments, comparePipeCalData, pipeText)
        : [],
    [isCompareMode, pipeFragments, comparePipeCalData, pipeText],
  );

  const [diameterRange, setDiameterRange] = useState<
    [number, number] | undefined
  >();
  const [elevationRange, setElevationRange] = useState<
    [number, number] | undefined
  >();
  const [forceStyleAutoApplyVersion, setForceStyleAutoApplyVersion] =
    useState(0);

  const toggleCompareMode = useCallback(() => {
    setCompareMode((prev) => {
      if (!prev) markCompareOpenStart();
      return !prev;
    });
  }, []);

  const maps = useMemo(
    () =>
      [map, isCompareMode ? compareMap : undefined].filter(Boolean) as OlMap[],
    [compareMap, isCompareMode, map],
  );

  const deckLayers = useMemo(
    () =>
      [deckLayer, isCompareMode ? compareDeckLayer : undefined].filter(
        Boolean,
      ) as DeckLayer[],
    [compareDeckLayer, deckLayer, isCompareMode],
  );

  const buildPipeFragments = useCallback((instance: TileFeatureInstance) => {
    const tileCoordinates = lineStringFromFlatCoordinates(
      instance.flatCoordinates,
      instance.stride,
    );
    const clippedParts = clipLineStringPartsToExtent(
      tileCoordinates,
      instance.tileExtent,
    );
    return clippedParts.flatMap((clippedCoordinates, partIndex) => {
      const path = coordinatesToLonLat(clippedCoordinates);
      const lineStringFeature = lineString(path);
      const fragmentLength = length(lineStringFeature);
      if (fragmentLength <= 0) return [];

      const timestamps = [0];
      let cumulativeLength = 0;
      for (let i = 1; i < path.length; i += 1) {
        cumulativeLength += length(lineString([path[i - 1], path[i]]));
        timestamps.push((cumulativeLength / fragmentLength) * 10);
      }

      const midPoint = along(lineStringFeature, fragmentLength / 2).geometry
        .coordinates;
      const prevPoint = along(lineStringFeature, fragmentLength * 0.49).geometry
        .coordinates;
      const nextPoint = along(lineStringFeature, fragmentLength * 0.51).geometry
        .coordinates;
      let lineAngle = bearing(prevPoint, nextPoint);
      lineAngle = -lineAngle + 90;
      if (lineAngle < -90 || lineAngle > 90) {
        lineAngle += 180;
      }

      return [
        {
          instanceKey: `${instance.instanceKey}/${partIndex}`,
          id: instance.featureId,
          diameter: instance.properties.diameter || 0,
          length: instance.properties.length || fragmentLength * 1000,
          path,
          position: midPoint,
          angle: lineAngle,
          timestamps,
          fragmentLength,
        },
      ];
    });
  }, []);

  const publishActiveTileSnapshot = useCallback(
    (targetMap: OlMap) => {
      const zoom = targetMap.getView().getZoom() ?? 0;
      const junctionSnapshot = junctionIndexRef.current?.getSnapshot(
        targetMap,
        zoom,
      );
      const pipeSnapshot = pipeIndexRef.current?.getSnapshot(targetMap, zoom);

      const nextJunctionData = Array.from(
        junctionSnapshot?.instancesById.values() ?? [],
      )
        .map((instances) => instances[0])
        .filter(Boolean)
        .map((instance) => {
          const [x, y] = lineStringFromFlatCoordinates(
            instance.flatCoordinates,
            instance.stride,
          )[0];
          return {
            id: instance.featureId,
            instanceKey: instance.instanceKey,
            position: toLonLat([x, y]),
            elevation: instance.properties.elevation || 0,
            demand: instance.properties.demand || 0,
          };
        })
        .sort((a, b) => String(a.id).localeCompare(String(b.id)));

      const nextPipeFragments = (pipeSnapshot?.instances ?? [])
        .filter((instance) => instance.geometryType.includes("Line"))
        .flatMap(buildPipeFragments)
        .sort((a, b) => a.instanceKey.localeCompare(b.instanceKey));

      const labelById = new Map<string, any>();
      nextPipeFragments.forEach((fragment) => {
        const previous = labelById.get(fragment.id);
        if (
          !previous ||
          fragment.fragmentLength > previous.fragmentLength ||
          (fragment.fragmentLength === previous.fragmentLength &&
            fragment.instanceKey.localeCompare(previous.instanceKey) < 0)
        ) {
          labelById.set(fragment.id, fragment);
        }
      });
      const nextPipeLabels = Array.from(labelById.values()).sort((a, b) =>
        String(a.id).localeCompare(String(b.id)),
      );

      setJunctionDataState(nextJunctionData);
      setPipeFragments(nextPipeFragments);
      setPipeDataState(nextPipeLabels);
      setElevationRange(
        getNumericRange(nextJunctionData.map((item) => item.elevation)),
      );
      setDiameterRange(
        getNumericRange(nextPipeLabels.map((item) => item.diameter)),
      );
    },
    [buildPipeFragments],
  );
  const operationalSources = useMemo(
    () =>
      createOperationalMapSources({
        mapUrl: MAP_URL,
        workspace: MAP_WORKSPACE,
      }),
    [MAP_URL, MAP_WORKSPACE],
  );
  const operationalResources = useMemo(
    () =>
      createOperationalMapResources({
        mapUrl: MAP_URL,
        workspace: MAP_WORKSPACE,
        extent: MAP_EXTENT,
        persistent: true,
        sources: operationalSources,
      }),
    [MAP_URL, MAP_WORKSPACE, MAP_EXTENT, operationalSources],
  );
  const { junctions: junctionSource, pipes: pipeSource } =
    operationalResources.sources;
  const { junctions: junctionsLayer, pipes: pipesLayer } =
    operationalResources.layers;

  // The map and layer instances are intentionally rebuilt only when workspace or extent changes.
  useEffect(() => {
    if (!mapRef.current) return;
    if (!canvasRef.current) {
      return;
    }
    isDisposingRef.current = false;

    junctionIndexRef.current = new TileFeatureIndex("junctions", junctionSource);
    pipeIndexRef.current = new TileFeatureIndex("pipes", pipeSource);

    const handleJunctionTileLoadEnd = (event: any) => {
      if (isDisposingRef.current) return;
      try {
        junctionIndexRef.current?.registerTile(event.tile);
        scheduleActiveTileSnapshot();
      } catch (error) {
        console.error("Junction tile load error:", error);
      }
    };
    const handlePipeTileLoadEnd = (event: any) => {
      if (isDisposingRef.current) return;
      try {
        pipeIndexRef.current?.registerTile(event.tile);
        scheduleActiveTileSnapshot();
      } catch (error) {
        console.error("Pipe tile load error:", error);
      }
    };
    // 监听 junctionsLayer 的 visible 变化
    const handleJunctionVisibilityChange = () => {
      const isVisible = junctionsLayer.getVisible();
      setShowJunctionTextLayer(isVisible);
    };
    // 监听 pipesLayer 的 visible 变化
    const handlePipeVisibilityChange = () => {
      const isVisible = pipesLayer.getVisible();
      setShowPipeTextLayer(isVisible);
    };
    // 添加事件监听器
    junctionsLayer.on("change:visible", handleJunctionVisibilityChange);
    pipesLayer.on("change:visible", handlePipeVisibilityChange);
    const map = new OlMap({
      target: mapRef.current,
      view: new View({
        maxZoom: 24,
        projection: "EPSG:3857",
      }),
      // 图层依面、线、点、标注次序添加
      layers: operationalResources.orderedLayers.slice(),
      controls: [],
    });
    const scheduleActiveTileSnapshot = debounce(
      () => publishActiveTileSnapshot(map),
      50,
    );
    junctionSource.on("tileloadend", handleJunctionTileLoadEnd);
    pipeSource.on("tileloadend", handlePipeTileLoadEnd);
    map.getInteractions().forEach(markMapResourcePersistent);
    setMap(map);

    // 恢复上次视图；如果没有则适配 MAP_EXTENT
    try {
      const stored = localStorage.getItem(MAP_VIEW_STORAGE_KEY);
      if (stored) {
        const viewState = JSON.parse(stored);
        if (
          viewState &&
          Array.isArray(viewState.center) &&
          viewState.center.length === 2 &&
          typeof viewState.zoom === "number" &&
          viewState.zoom >= 11 &&
          viewState.zoom <= 24
        ) {
          map.getView().setCenter(viewState.center);
          map.getView().setZoom(viewState.zoom);
        } else {
          map.getView().fit(MAP_EXTENT, {
            padding: [50, 50, 50, 50],
            duration: 1000,
          });
        }
      } else {
        map.getView().fit(MAP_EXTENT, {
          padding: [50, 50, 50, 50],
          duration: 1000,
        });
      }
    } catch (err) {
      console.warn("Restore map view failed", err);
      map.getView().fit(MAP_EXTENT, {
        padding: [50, 50, 50, 50],
        duration: 1000,
      });
    }
    // 视图稳定后同步 Deck 数据并持久化，避免移动过程中重复扫描瓦片。
    const handleViewChange = debounce(() => {
      if (isDisposingRef.current) return;
      const view = map.getView();
      const zoom = view.getZoom() || 0;
      setCurrentZoom(zoom);
      junctionIndexRef.current?.scanLoadedTiles();
      pipeIndexRef.current?.scanLoadedTiles();
      scheduleActiveTileSnapshot();
      try {
        const center = view.getCenter();
        if (center) {
          localStorage.setItem(
            MAP_VIEW_STORAGE_KEY,
            JSON.stringify({ center, zoom }),
          );
        }
      } catch (err) {
        console.warn("Save map view failed", err);
      }
    }, 250);
    map.getView().on("change", handleViewChange);

    // 初始化当前缩放级别并强制触发瓦片加载
    const initializeTimer = window.setTimeout(() => {
      if (isDisposingRef.current) return;
      const initialZoom = map.getView().getZoom() || 11;
      setCurrentZoom(initialZoom);
      junctionIndexRef.current?.scanLoadedTiles();
      pipeIndexRef.current?.scanLoadedTiles();
      scheduleActiveTileSnapshot();
      // 强制触发地图渲染，让瓦片加载事件触发
      map.render();
    }, 100);

    // 初始化 deck.gl
    const deck = new Deck({
      initialViewState: {
        longitude: 0,
        latitude: 0,
        zoom: 1,
      },
      canvas: canvasRef.current,
      controller: false, // 由 OpenLayers 控制视图
      layers: [],
    });
    const deckLayer = markMapResourcePersistent(
      new DeckLayer(deck, canvasRef.current, {
        name: "deckLayer",
        value: "deckLayer",
      }),
    );
    deckLayerRef.current = deckLayer;
    setDeckLayer(deckLayer);
    map.addLayer(deckLayer);

    // 清理函数
    return () => {
      isDisposingRef.current = true;
      window.clearTimeout(initializeTimer);
      scheduleActiveTileSnapshot.cancel();
      handleViewChange.cancel();
      junctionSource.un("tileloadend", handleJunctionTileLoadEnd);
      pipeSource.un("tileloadend", handlePipeTileLoadEnd);
      map.getView().un("change", handleViewChange);
      junctionsLayer.un("change:visible", handleJunctionVisibilityChange);
      pipesLayer.un("change:visible", handlePipeVisibilityChange);
      if (deckLayerRef.current && !deckLayerRef.current.isDisposedLayer()) {
        try {
          map.removeLayer(deckLayerRef.current);
        } catch {
          // Layer may have already been removed during teardown.
        }
        deckLayerRef.current.disposeDeck();
      }
      deckLayerRef.current = null;
      setDeckLayer(undefined);
      // React Strict Mode re-runs effects with the same memoized layer instances.
      // Detach and clear them here, but leave final layer/source disposal to GC.
      disposeMapResources(map, { disposeLayers: false });
      junctionIndexRef.current = null;
      pipeIndexRef.current = null;
      setJunctionDataState([]);
      setPipeDataState([]);
      setPipeFragments([]);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [MAP_WORKSPACE, MAP_EXTENT]);

  useEffect(() => {
    if (!isCompareMode) {
      isCompareDisposingRef.current = true;
      setCompareJunctionCalData([]);
      setComparePipeCalData([]);
      return;
    }
    if (!map || !compareMapRef.current || !compareCanvasRef.current) return;

    isCompareDisposingRef.current = false;
    const compareResources = createOperationalMapResources({
      mapUrl: MAP_URL,
      workspace: MAP_WORKSPACE,
      extent: MAP_EXTENT,
      sources: operationalSources,
    });
    const nextCompareMap = new OlMap({
      target: compareMapRef.current,
      view: map.getView(),
      layers: compareResources.orderedLayers.slice(),
      controls: [],
    });
    const handleCompareRenderComplete = () => markCompareOpenReady();
    nextCompareMap.once("rendercomplete", handleCompareRenderComplete);
    nextCompareMap.getAllLayers().forEach((layer) => {
      const layerId = layer.get("value");
      if (!layerId) return;
      const primaryLayer = map
        .getAllLayers()
        .find((currentLayer) => currentLayer.get("value") === layerId);
      if (primaryLayer) {
        layer.setVisible(primaryLayer.getVisible());
      }
    });
    setCompareMap(nextCompareMap);

    const compareDeck = new Deck({
      initialViewState: {
        longitude: 0,
        latitude: 0,
        zoom: 1,
      },
      canvas: compareCanvasRef.current,
      controller: false,
      layers: [],
    });
    const nextCompareDeckLayer = new DeckLayer(
      compareDeck,
      compareCanvasRef.current,
      {
        name: "compareDeckLayer",
        value: "deckLayer",
      },
    );
    compareDeckLayerRef.current = nextCompareDeckLayer;
    setCompareDeckLayer(nextCompareDeckLayer);
    nextCompareMap.addLayer(nextCompareDeckLayer);

    const resizeTimerId = window.setTimeout(() => {
      map.updateSize();
      nextCompareMap.updateSize();
    }, 0);

    return () => {
      isCompareDisposingRef.current = true;
      window.clearTimeout(resizeTimerId);
      nextCompareMap.un("rendercomplete", handleCompareRenderComplete);
      if (
        compareDeckLayerRef.current &&
        !compareDeckLayerRef.current.isDisposedLayer()
      ) {
        try {
          nextCompareMap.removeLayer(compareDeckLayerRef.current);
        } catch {
          // Layer may have already been removed during teardown.
        }
        compareDeckLayerRef.current.disposeDeck();
      }
      compareDeckLayerRef.current = null;
      setCompareDeckLayer(undefined);
      setCompareMap(undefined);
      disposeMapResources(nextCompareMap, { disposeSources: false });
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isCompareMode, map]);

  useEffect(() => {
    const resizeTimerId = window.setTimeout(() => {
      map?.updateSize();
      compareMap?.updateSize();
    }, 0);

    return () => {
      window.clearTimeout(resizeTimerId);
    };
  }, [compareMap, isCompareMode, map]);

  useEffect(() => {
    setCurrentTime(
      getRoundedCurrentTimelineMinutes(new Date(), stepMinutes, durationMinutes),
    );
    setSelectedDate(new Date());
    setSchemeName("");
    setCurrentJunctionCalData([]);
    setCurrentPipeCalData([]);
    setCompareJunctionCalData([]);
    setComparePipeCalData([]);
    setCompareMode(false);
    setShowJunctionTextLayer(false);
    setShowPipeTextLayer(false);
    setShowJunctionId(false);
    setShowPipeId(false);
    setShowContourLayer(false);
    setContours([]);
    setContourLayerAvailable(false);
    setWaterflowLayerAvailable(false);
    setShowWaterflowLayer(false);
    setForceStyleAutoApplyVersion(0);

    return () => {
      if (!map) return;
      cleanupTransientMapResources(map);
      deckLayerRef.current?.resetSessionLayers();
      operationalResources.resetStyles();
    };
  }, [durationMinutes, pathname, map, operationalResources, stepMinutes]);

  // 当数据变化时，更新 deck.gl 图层
  useEffect(() => {
    const syncDeckOverlay = (
      targetDeckLayer: DeckLayer | null,
      targetJunctionData: any[],
      targetPipeData: any[],
      disposing: boolean,
    ) => {
      if (disposing || !targetDeckLayer || targetDeckLayer.isDisposedLayer()) {
        return;
      }
      const shouldShowJunctionText =
        (showJunctionTextLayer || showJunctionId) &&
        currentZoom >= 15 &&
        currentZoom <= 24 &&
        targetJunctionData.length > 0;
      const shouldShowPipeText =
        (showPipeTextLayer || showPipeId) &&
        currentZoom >= 15 &&
        currentZoom <= 24 &&
        targetPipeData.length > 0;
      const shouldShowContour =
        showContourLayer &&
        currentZoom >= 11 &&
        currentZoom <= 24 &&
        targetJunctionData.length > 0;

      if (!shouldShowJunctionText) {
        targetDeckLayer.removeDeckLayer("junctionTextLayer");
      }
      if (!shouldShowPipeText) {
        targetDeckLayer.removeDeckLayer("pipeTextLayer");
      }
      if (!shouldShowContour) {
        targetDeckLayer.removeDeckLayer("junctionContourLayer");
      }
      if (!shouldShowJunctionText && !shouldShowPipeText && !shouldShowContour) {
        return;
      }

      const junctionTextLayer = shouldShowJunctionText
        ? new TextLayer({
            id: "junctionTextLayer",
            name: "节点文字",
            zIndex: 10,
            data: targetJunctionData,
            getPosition: (d: any) => d.position,
            fontFamily: "Monaco, monospace",
            getText: (d: any) => {
              let idPart = showJunctionId ? d.id : "";
              let propPart = "";
              if (showJunctionTextLayer && d[junctionText] !== undefined) {
                const value = (d[junctionText] as number).toFixed(3);
                propPart = `${value}`;
              }
              if (idPart && propPart) return `${idPart} - ${propPart}`;
              return idPart || propPart;
            },
            getSize: 14,
            fontWeight: "bold",
            getColor: [33, 37, 41],
            getAngle: 0,
            getTextAnchor: "middle",
            getAlignmentBaseline: "center",
            getPixelOffset: [0, -10],
            visible: true,
            updateTriggers: {
              getText: [showJunctionId, showJunctionTextLayer, junctionText],
            },
            extensions: [new CollisionFilterExtension()],
            collisionTestProps: {
              sizeScale: 3,
            },
            characterSet: "auto",
            fontSettings: {
              sdf: true,
              fontSize: 64,
              buffer: 6,
            },
          })
        : null;

      const pipeTextLayer = shouldShowPipeText
        ? new TextLayer({
            id: "pipeTextLayer",
            name: "管道文字",
            zIndex: 10,
            data: targetPipeData,
            getPosition: (d: any) => d.position,
            fontFamily: "Monaco, monospace",
            getText: (d: any) => {
              let idPart = showPipeId ? d.id : "";
              let propPart = "";
              if (showPipeTextLayer && d[pipeText] !== undefined) {
                let value;
                if (pipeText === "unit_headloss") {
                  value = (
                    (d["unit_headloss"] / (d["length"] / 1000)) as number
                  ).toFixed(3);
                } else {
                  value = Math.abs(d[pipeText] as number).toFixed(3);
                }
                propPart = `${value}`;
              }
              if (idPart && propPart) return `${idPart} - ${propPart}`;
              return idPart || propPart;
            },
            getSize: 14,
            fontWeight: "bold",
            getColor: [33, 37, 41],
            getAngle: (d: any) => d.angle || 0,
            getPixelOffset: [0, -8],
            getTextAnchor: "middle",
            getAlignmentBaseline: "bottom",
            visible: true,
            updateTriggers: {
              getText: [showPipeId, showPipeTextLayer, pipeText],
            },
            extensions: [new CollisionFilterExtension()],
            collisionTestProps: {
              sizeScale: 3,
            },
            characterSet: "auto",
            fontSettings: {
              sdf: true,
              fontSize: 64,
              buffer: 6,
            },
          })
        : null;

      const contourLayer = shouldShowContour
        ? new ContourLayer({
            id: "junctionContourLayer",
            name: "等值线",
            data: targetJunctionData,
            aggregation: "MEAN",
            cellSize: 600,
            strokeWidth: 0,
            contours: contours,
            getPosition: (d) => d.position,
            getWeight: (d: any) =>
              (d[junctionText] as number) < 0 ? 0 : (d[junctionText] as number),
            opacity: 1,
            visible: true,
            updateTriggers: {
              getWeight: [targetJunctionData, junctionText],
            },
          })
        : null;

      if (
        junctionTextLayer &&
        targetDeckLayer.getDeckLayerById("junctionTextLayer")
      ) {
        targetDeckLayer.updateDeckLayer("junctionTextLayer", junctionTextLayer);
      } else if (junctionTextLayer) {
        targetDeckLayer.addDeckLayer(junctionTextLayer);
      }
      if (pipeTextLayer && targetDeckLayer.getDeckLayerById("pipeTextLayer")) {
        targetDeckLayer.updateDeckLayer("pipeTextLayer", pipeTextLayer);
      } else if (pipeTextLayer) {
        targetDeckLayer.addDeckLayer(pipeTextLayer);
      }
      if (
        contourLayer &&
        targetDeckLayer.getDeckLayerById("junctionContourLayer")
      ) {
        targetDeckLayer.updateDeckLayer("junctionContourLayer", contourLayer);
      } else if (contourLayer) {
        targetDeckLayer.addDeckLayer(contourLayer);
      }
    };

    syncDeckOverlay(
      deckLayerRef.current,
      mergedJunctionData,
      mergedPipeData,
      isDisposingRef.current,
    );
    if (isCompareMode) {
      syncDeckOverlay(
        compareDeckLayerRef.current,
        mergedCompareJunctionData,
        mergedComparePipeData,
        isCompareDisposingRef.current,
      );
    }
  }, [
    mergedJunctionData,
    mergedPipeData,
    mergedCompareJunctionData,
    mergedComparePipeData,
    isCompareMode,
    junctionText,
    pipeText,
    currentZoom,
    showJunctionTextLayer,
    showPipeTextLayer,
    showJunctionId,
    showPipeId,
    showContourLayer,
    contours,
  ]);

  // 控制流动动画开关
  useEffect(() => {
    const hasFlowData = pipeText === "flow" && currentPipeCalData.length > 0;
    const shouldShowWaterflow =
      isWaterflowLayerAvailable &&
      showWaterflowLayer &&
      hasFlowData &&
      currentZoom >= 12 &&
      currentZoom <= 24;

    let animationFrameId: number;

    const syncWaterflowLayer = (
      targetDeckLayer: DeckLayer | null,
      targetPipeFragments: any[],
      disposing: boolean,
    ) => {
      if (disposing || !targetDeckLayer || targetDeckLayer.isDisposedLayer()) {
        return;
      }
      if (!shouldShowWaterflow || targetPipeFragments.length === 0) {
        targetDeckLayer.removeDeckLayer("waterflowLayer");
        return;
      }
      const animationDuration = 10;
      const bufferTime = 2;
      const loopLength = animationDuration + bufferTime;
      const currentFrameTime = (Date.now() / 1000) % loopLength;

      const waterflowLayer = new TripsLayer({
        id: "waterflowLayer",
        name: "水流",
        data: targetPipeFragments,
        getObjectId: (d: any) => d.instanceKey,
        getPath: (d) => d.path,
        getTimestamps: (d) => d.timestamps,
        getColor: [0, 220, 255],
        opacity: 0.8,
        visible: true,
        widthMinPixels: 5,
        jointRounded: true,
        trailLength: 2,
        currentTime: currentFrameTime,
      });

      if (targetDeckLayer.getDeckLayerById("waterflowLayer")) {
        targetDeckLayer.updateDeckLayer("waterflowLayer", waterflowLayer);
      } else {
        targetDeckLayer.addDeckLayer(waterflowLayer);
      }
    };

    const animate = () => {
      syncWaterflowLayer(
        deckLayerRef.current,
        mergedPipeFragments,
        isDisposingRef.current,
      );
      if (isCompareMode) {
        syncWaterflowLayer(
          compareDeckLayerRef.current,
          mergedComparePipeFragments,
          isCompareDisposingRef.current,
        );
      }
      if (shouldShowWaterflow) {
        animationFrameId = requestAnimationFrame(animate);
      }
    };
    animate();

    // 清理函数:取消动画帧
    return () => {
      if (animationFrameId) {
        cancelAnimationFrame(animationFrameId);
      }
    };
  }, [
    currentPipeCalData,
    currentZoom,
    mergedPipeFragments,
    mergedComparePipeFragments,
    isCompareMode,
    pipeText,
    isWaterflowLayerAvailable,
    showWaterflowLayer,
  ]);

  return (
    <>
      <DataContext.Provider
        value={{
          currentTime,
          setCurrentTime,
          selectedDate,
          setSelectedDate,
          schemeName,
          setSchemeName,
          currentJunctionCalData,
          setCurrentJunctionCalData,
          currentPipeCalData,
          setCurrentPipeCalData,
          compareJunctionCalData,
          setCompareJunctionCalData,
          comparePipeCalData,
          setComparePipeCalData,
          isCompareMode,
          setCompareMode,
          toggleCompareMode,
          setShowJunctionTextLayer,
          setShowPipeTextLayer,
          setShowJunctionId,
          setShowPipeId,
          showJunctionId,
          showPipeId,
          showContourLayer,
          setShowContourLayer,
          isContourLayerAvailable,
          showWaterflowLayer,
          setContourLayerAvailable,
          isWaterflowLayerAvailable,
          setWaterflowLayerAvailable,
          setShowWaterflowLayer,
          setJunctionText,
          setPipeText,
          junctionText,
          pipeText,
          setContours,
          deckLayer,
          compareDeckLayer,
          deckLayers,
          compareMap,
          maps,
          diameterRange,
          elevationRange,
          forceStyleAutoApplyVersion,
          setForceStyleAutoApplyVersion,
        }}
      >
        <MapContext.Provider value={map}>
          <div className="relative w-full h-full">
            <div className="flex w-full h-full">
              <div
                className={`relative h-full ${isCompareMode ? "w-1/2" : "w-full"}`}
              >
                <div ref={mapRef} className="w-full h-full"></div>
                <canvas
                  ref={canvasRef}
                  className="pointer-events-none absolute inset-0"
                />
                {isCompareMode && (
                  <div className="pointer-events-none absolute right-4 top-4 rounded-md bg-black/55 px-3 py-1 text-sm font-medium text-white">
                    方案模拟
                  </div>
                )}
              </div>
              {isCompareMode && (
                <div className="relative h-full w-1/2 border-l border-white/40">
                  <div ref={compareMapRef} className="w-full h-full"></div>
                  <canvas
                    ref={compareCanvasRef}
                    className="pointer-events-none absolute inset-0"
                  />
                  <div className="pointer-events-none absolute left-4 top-4 rounded-md bg-black/55 px-3 py-1 text-sm font-medium text-white">
                    实时模拟
                  </div>
                </div>
              )}
            </div>
            {isCompareMode && (
              <div className="pointer-events-none absolute inset-y-0 left-1/2 z-10 w-px -translate-x-1/2 bg-white/85 shadow-[0_0_0_1px_rgba(15,23,42,0.18)]" />
            )}
            <MapTools />
            {children}
          </div>
        </MapContext.Provider>
      </DataContext.Provider>
    </>
  );
};

export default MapComponent;
