/**
 * 高德地图适配器
 *
 * 作用：
 * 1. 封装高德地图 JS API 2.0 的具体实现
 * 2. 实现 MapAdapter 抽象接口，提供统一的地图操作能力
 * 3. 处理高德地图 SDK 的加载、初始化等底层细节
 *
 * 特性：
 * - 支持懒加载高德地图 SDK，首次使用时才加载
 * - 支持 3D 视图和倾斜视角
 * - 支持矢量地图和卫星地图切换
 * - 管理业务图层，支持按 ID 清理
 *
 * @class AMapAdapter
 * @extends MapAdapter
 */

import AMapLoader from '@amap/amap-jsapi-loader'
import MapAdapter from '@vue2-client/base-client/components/common/AfMap/map/MapAdapter'
import { fromLonLat, transform } from 'ol/proj'

export default class AMapAdapter extends MapAdapter {
  /**
   * 构造函数
   *
   * 初始化适配器状态，包括地图实例、图层管理和 SDK 命名空间
   */
  constructor () {
    super()

    /**
     * 高德地图实例
     * @type {AMap.Map|null}
     */
    this.map = null

    /**
     * 业务图层映射表
     * 以图层 ID 为 key，存储业务层创建的图层/覆盖物实例
     * 用于后续的图层管理和清理操作
     *
     * @type {Object<string, AMap.Marker|AMap.Polygon|AMap.TileLayer>}
     */
    this.layers = {}

    /**
     * 组数据映射表
     * 以组 ID 为 key，存储组数据信息
     * 格式：{ id, name, type, visible, items: [] }
     *
     * @type {Object<string, object>}
     */
    this.groups = {}

    /**
     * 组 OverlayGroup 缓存
     * 用于保持同一组复用同一个容器，便于显隐与数据刷新
     *
     * @type {Object<string, AMap.OverlayGroup>}
     */
    this.overlayGroups = {}

    /**
     * 高德地图 SDK 命名空间
     * 缓存加载后的 AMap 对象，避免重复加载
     *
     * @type {object|null}
     */
    this.AMap = null

    this.satelliteLayer = null
    this.roadNetLayer = null
    this.mouseTool = null // 鼠标工具实例（测距/框选复用）

    // 测距相关
    this.measureResults = []
    this.measureResultCb = null
    this.measureDrawHandler = null
    this.areaDrawHandler = null

    this.currentViewMode = '3D'

    this.selectDrawHandler = null
    // 框选结果覆盖物缓存
    this.selectOverlays = { rectangle: [], circle: [], polygon: [] }

    /**
     * WMS点击查询相关
     */
    this.wmsClickCallback = null
    this.wmsClickEnabled = false
    this.wmsClickHandler = null

    /**
     * WMS鼠标悬停样式相关
     */
    this.wmsHoverEnabled = false
    this.wmsHoverHandler = null
    this.wmsHoverMouseOutHandler = null
    this.mapContainer = null // 地图容器DOM元素，用于改变鼠标样式
    this.wmsHoverCheckTimer = null // 防抖定时器
    this.wmsHoverPendingRequest = null // 正在进行的请求

    /**
     * 组闪烁定时器
     */
    this.blinkTimers = {}

    /**
     * 组数据刷新定时器
     * 以组 ID 为 key，存储定时刷新标记点数据的定时器
     * @type {Object<string, number>}
     */
    this.refreshTimers = {}

    /**
     * 分析画线句柄
     */
    this.lineDrawHandler = null

    /**
     * 通用地图点击相关
     */
    this.mapClickCallback = null
    this.mapClickEnabled = false
    this.mapClickHandler = null

    /**
     * 单次绘制线相关
     */
    // 初始化状态
    this._straightLineStart = null // 起点坐标
    this._straightLinePolyline = null // 预览线
    // 点击事件
    this._straightLineClickHandler = null
    // 移动事件
    this._straightLineMoveHandler = null

    /**
     * 信息窗口实例
     * @type {AMap.InfoWindow|null}
     */
    this.infoWindow = null

    /**
     * 轨迹播放状态对象
     * 所有轨迹播放相关的状态都集中在这里，便于管理和序列化
     */
    this.trajectoryState = {
      // 数据相关
      segments: [], // 轨迹段数据
      config: null, // 轨迹配置

      // 播放状态
      currentIndex: 0, // 当前播放段索引
      progress: 0, // 当前段进度 0-1
      status: 'idle', // 播放状态: 'idle' | 'playing' | 'paused' | 'finished'
      speed: 1, // 当前播放速度倍数

      // 内部状态（不对外暴露，但保留在 state 中便于管理）
      _distanceTraveled: 0, // 当前段已行驶距离
      _segmentLengths: [], // 当前段各线段长度
      _totalLength: 0, // 当前段总长度
      _coordinates: [], // 当前段坐标
      _lastTime: 0, // 上次动画时间

      // 地图元素引用（不序列化）
      _polylines: [], // 轨迹线数组
      _traveledPolylines: [], // 已播放轨迹线数组
      _carMarker: null, // 小车标记
      _startMarkers: [], // 起点标记数组
      _endMarkers: [], // 终点标记数组
      _animationTimer: null // 动画定时器
    }
  }

  /**
   * 初始化高德地图实例
   *
   * @param {string|HTMLElement} container - 地图容器 DOM 或选择器
   * @param {object} options - 地图初始化配置
   * @param {Array<number>} options.center - 中心点坐标 [经度, 纬度]
   * @param {number} options.zoom - 缩放级别
   * @param {Array<number>} options.zooms - 缩放范围 [最小, 最大]
   * @returns {Promise<AMap.Map>} 高德地图实例
   */
  async initMap (container, options = {}) {
    // 懒加载高德地图 SDK，首次使用时才加载
    this.AMap = await this.ensureSdkLoaded(options.AMAPKEY)

    // 处理容器参数：支持字符串选择器或 DOM 元素
    const target = typeof container === 'string'
      ? document.querySelector(container)
      : container

    // 合并用户配置和默认配置
    const mapOptions = {
      ...options, // 用户自定义配置
      pitch: 45, // 地图倾斜角度（3D 效果）
      viewMode: this.currentViewMode, // 3D 视图模式
      animateEnable: true, // 启用动画效果
      renderWorldCopies: false, // 不渲染世界副本，减少 Canvas 操作
      doubleClickZoom: true, // 双击缩放
      keyboardEnable: false, // 禁用键盘控制
      mapStyle: 'amap://styles/normal',
      // 性能优化：减少地图渲染负担
      renderEngine: 'webgl', // 🔑 GPU 渲染
      jogEnable: false, // 禁用拖拽时的惯性效果，减少计算
      pitchEnable: true, // 保持俯仰功能
      rotateEnable: true, // 保持旋转功能
      // 性能优化：限制缩放范围，避免加载过多瓦片
      zooms: options.zooms || [3, 18]
    }

    // 创建地图实例
    this.map = new this.AMap.Map(target, mapOptions)

    // 保存地图容器DOM引用，用于改变鼠标样式
    this.mapContainer = typeof target === 'string'
      ? document.querySelector(target)
      : target

    // 比例尺控件（右下角）
    if (this.AMap.Scale) {
      const scaleCtrl = new this.AMap.Scale({
        position: 'RB'
      })
      this.map.addControl(scaleCtrl)
    }

    this.initSatelliteLayers()

    return this.map
  }

  /**
   * 初始化WMS点击查询事件监听
   */
  initWmsClickHandler () {
    if (!this.map) return

    // 移除旧的事件监听
    if (this.wmsClickHandler) {
      this.map.off('click', this.wmsClickHandler)
    }

    // 创建新的事件监听
    this.wmsClickHandler = (evt) => {
      if (!this.wmsClickEnabled || !this.wmsClickCallback) return

      const lnglat = evt.lnglat
      if (lnglat) {
        const coordinate = [lnglat.lng, lnglat.lat]
        // 调用回调函数，传递坐标 [lng, lat]
        if (this.wmsClickCallback) {
          this.wmsClickCallback(coordinate)
        }
      }
    }

    // 绑定点击事件
    this.map.on('click', this.wmsClickHandler)
  }

  /**
   * 启用/禁用 WMS 图层鼠标悬停样式
   * 当鼠标悬停在 WMS 图层上时，改变鼠标样式为 pointer
   * 通过 GetFeatureInfo 检测鼠标位置是否有 WMS 数据
   *
   * @param {boolean} enabled - 是否启用
   */
  setWmsHoverEnabled (enabled) {
    this.wmsHoverEnabled = enabled

    if (!this.map || !this.mapContainer) return

    // 清除防抖定时器和正在进行的请求
    if (this.wmsHoverCheckTimer) {
      clearTimeout(this.wmsHoverCheckTimer)
      this.wmsHoverCheckTimer = null
    }
    if (this.wmsHoverPendingRequest) {
      this.wmsHoverPendingRequest.abort()
      this.wmsHoverPendingRequest = null
    }

    // 移除旧的事件监听
    if (this.wmsHoverHandler) {
      this.map.off('mousemove', this.wmsHoverHandler)
      this.wmsHoverHandler = null
    }
    if (this.wmsHoverMouseOutHandler) {
      this.map.off('mouseout', this.wmsHoverMouseOutHandler)
      this.wmsHoverMouseOutHandler = null
    }

    if (!enabled) {
      // 禁用时恢复默认鼠标样式
      if (this.mapContainer) {
        this.mapContainer.style.cursor = ''
      }
      return
    }

    // 创建鼠标移动事件监听（带防抖）
    this.wmsHoverHandler = (evt) => {
      if (!this.wmsHoverEnabled || !this.mapContainer) return

      const lnglat = evt.lnglat
      if (!lnglat) return

      const coordinate = [lnglat.lng, lnglat.lat]

      // 清除之前的定时器
      if (this.wmsHoverCheckTimer) {
        clearTimeout(this.wmsHoverCheckTimer)
      }

      // 防抖：延迟 100ms 后检测
      this.wmsHoverCheckTimer = setTimeout(() => {
        this.checkWmsAtCoordinate(coordinate)
      }, 100)
    }

    // 鼠标移出地图时恢复默认样式
    const mouseOutHandler = () => {
      if (this.mapContainer) {
        this.mapContainer.style.cursor = ''
      }
      // 清除定时器和请求
      if (this.wmsHoverCheckTimer) {
        clearTimeout(this.wmsHoverCheckTimer)
        this.wmsHoverCheckTimer = null
      }
      if (this.wmsHoverPendingRequest) {
        this.wmsHoverPendingRequest.abort()
        this.wmsHoverPendingRequest = null
      }
    }

    // 绑定事件
    this.map.on('mousemove', this.wmsHoverHandler)
    this.map.on('mouseout', mouseOutHandler)

    // 保存 mouseOutHandler 以便后续移除
    this.wmsHoverMouseOutHandler = mouseOutHandler
  }

  /**
   * 检测指定坐标位置是否有 WMS 数据
   * @param {Array<number>} coordinate - 坐标 [lng, lat]
   */
  async checkWmsAtCoordinate (coordinate) {
    if (!this.wmsHoverEnabled || !this.mapContainer) return

    // 获取所有可见的 WMS 图层
    const wmsGroups = this.getGroupsByType('wms')
      .filter(group => group.visible === true)

    if (wmsGroups.length === 0) {
      // 没有可见的 WMS 图层，恢复默认样式
      this.mapContainer.style.cursor = ''
      return
    }

    // 取消之前的请求
    if (this.wmsHoverPendingRequest) {
      this.wmsHoverPendingRequest.abort()
    }

    // 遍历所有可见的 WMS 图层，检测是否有数据
    for (const group of wmsGroups) {
      try {
        const url = this.getWmsFeatureInfoUrl(group.id, coordinate)
        if (!url) continue

        // 创建 AbortController 用于取消请求
        const controller = new AbortController()
        this.wmsHoverPendingRequest = controller

        // 发送 GetFeatureInfo 请求
        const response = await fetch(url, {
          signal: controller.signal
        })

        if (!response.ok) continue

        const data = await response.json()

        // 如果返回了数据，说明鼠标在 WMS 图层上
        if (data.features && data.features.length > 0) {
          this.mapContainer.style.cursor = 'pointer'
          this.wmsHoverPendingRequest = null
          return
        }
      } catch (error) {
        // 忽略取消请求的错误
        if (error.name !== 'AbortError') {
          console.debug('WMS hover check error:', error)
        }
        continue
      }
    }

    // 所有图层都没有数据，恢复默认样式
    this.mapContainer.style.cursor = ''
    this.wmsHoverPendingRequest = null
  }

  /**
   * 设置WMS点击查询回调
   * @param {Function} callback - 回调函数，参数为 (coordinate: Array<number>)
   */
  setWmsClickCallback (callback) {
    this.wmsClickCallback = callback
  }

  /**
   * 设置WMS点击查询启用状态
   * @param {boolean} enabled
   */
  setWmsClickEnabled (enabled) {
    this.wmsClickEnabled = enabled
  }

  /**
   * 设置通用地图点击回调
   */
  setMapClickCallback (callback) {
    this.mapClickCallback = callback
    if (!this.mapClickHandler && this.map) {
      this.initMapClickHandler()
    }
  }

  /**
   * 设置通用地图点击启用状态
   */
  setMapClickEnabled (enabled) {
    this.mapClickEnabled = enabled
  }

  /**
   * 初始化通用地图点击监听
   */
  initMapClickHandler () {
    if (!this.map) return
    if (this.mapClickHandler) {
      this.map.off('click', this.mapClickHandler)
    }
    this.mapClickHandler = (evt) => {
      if (!this.mapClickEnabled || !this.mapClickCallback) return
      const lnglat = evt.lnglat
      if (lnglat) {
        this.mapClickCallback([lnglat.lng, lnglat.lat])
      }
    }
    this.map.on('click', this.mapClickHandler)
  }

  /**
   * 获取WMS图层的GetFeatureInfo URL（高德地图适配）
   * @param {string} layerId - 图层ID
   * @param {Array<number>} coordinate - 点击坐标 [lng, lat] (GCJ-02 或 已经转为 EPSG:3857)
   * @returns {string|null} GetFeatureInfo URL
   */
  getWmsFeatureInfoUrl (layerId, coordinate) {
    if (!this.map || !coordinate || coordinate.length < 2) {
      return null
    }

    const layer = this.layers[layerId]
    if (!layer) return null

    const group = this.groups[layerId]
    if (!group || group.type !== 'wms' || !group.wmsConfig) return null
    if (!group.visible) return null

    try {
      const wmsConfig = group.wmsConfig
      const { url, params } = wmsConfig

      // ========== 1. 地图视图信息 ==========
      const size = this.map.getSize()
      const bounds = this.map.getBounds()
      const [clickLng, clickLat] = coordinate

      // ========== 2. BBOX（直接使用 fromLonLat 转 EPSG:3857） ==========
      const ne = bounds.getNorthEast()
      const sw = bounds.getSouthWest()

      const sw3857 = fromLonLat([sw.lng, sw.lat])
      const ne3857 = fromLonLat([ne.lng, ne.lat])

      const bbox = `${sw3857[0]},${sw3857[1]},${ne3857[0]},${ne3857[1]}`

      // ========== 3. 点击点转 EPSG:3857 ==========
      const clicked3857 = fromLonLat([clickLng, clickLat])

      // 计算 I/J 像素
      const bboxWidth = ne3857[0] - sw3857[0]
      const bboxHeight = ne3857[1] - sw3857[1]

      const i = Math.floor(((clicked3857[0] - sw3857[0]) / bboxWidth) * size.width)
      const j = Math.floor(((ne3857[1] - clicked3857[1]) / bboxHeight) * size.height)

      // ========== 4. 构建 WMS GetFeatureInfo 请求 ==========
      const version = params.VERSION || '1.3.0'
      const targetCRS = 'EPSG:3857'

      const queryParams = {
        SERVICE: 'WMS',
        REQUEST: 'GetFeatureInfo',
        VERSION: version,
        FORMAT: params.FORMAT || 'image/png',
        INFO_FORMAT: 'application/json',
        LAYERS: params.LAYERS,
        QUERY_LAYERS: params.LAYERS,
        STYLES: params.STYLES || '',
        CRS: targetCRS,
        BBOX: bbox,
        WIDTH: size.width,
        HEIGHT: size.height
      }

      if (version === '1.3.0') {
        queryParams.I = i
        queryParams.J = j
      } else {
        queryParams.SRS = targetCRS
        queryParams.X = i
        queryParams.Y = j
      }

      // 拼装 URL
      const queryString = Object.keys(queryParams)
        .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
        .join('&')

      return `${url}?${queryString}`
    } catch (err) {
      console.error(`获取 WMS 功能信息 URL 出错，图层: ${layerId}`, err)
      return null
    }
  }

  bindViewChange (callback) {
    this.viewChangeCb = callback
    if (!this.map) return
    this.map.on('moveend', () => {
      const state = this.getMapState()
      if (state && state.center && this.viewChangeCb) {
        this.viewChangeCb(state.center)
      }
    })
  }

  /**
   * 批量添加标记点，并支持组级点击回调
   *
   * @param {Function} getDataFn 获取标记点数据的函数，返回 Promise<Array<object>>
   * @param {object} options 组配置选项
   * @param {string} options.groupId 组数据唯一标识（必填）
   * @param {string} options.groupName 组数据名称（必填）
   * @param {boolean} [options.visible=true] 默认是否显示，默认 true
   * @param {boolean} [options.isAutoRefresh=false] 是否定时刷新，默认 false
   * @param {number} [options.refreshInterval=10] 定时间隔（秒），默认 10 秒
   * @param {boolean} [options.showLabel=false] 是否显示文本标签，默认 false
   * @param {string} [options.labelPosition='bottom'] 标签位置，默认 'bottom'，可选值：'top'|'right'|'bottom'|'left'|'center'
   * @param {Function} [onClick] 点位点击回调 (payload) => void
   * @returns {Promise<Array<AMap.Marker>>} 标记点实例数组
   */
  async addMarker (getDataFn, options = {}, onClick) {
    if (!this.map) throw new Error('Map not initialized')

    const {
      groupId,
      groupName,
      visible = true,
      isAutoRefresh = false,
      refreshInterval = 10,
      showLabel = false,
      labelPosition = 'bottom'
    } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 如果组已存在，先清理旧的标记点和定时器
    if (this.groups[groupId]) {
      this.stopMarkerRefresh(groupId)
      const oldGroup = this.groups[groupId]
      if (oldGroup.items) {
        oldGroup.items.forEach((item) => {
          const marker = this.layers[item.id]
          if (marker) {
            this.map.remove(marker)
            delete this.layers[item.id]
          }
        })
      }
    }

    // 调用数据获取函数获取最新数据
    const markerOptionsList = await getDataFn()

    const { markers, items } = this.buildMarkers(markerOptionsList, { showLabel, labelPosition })

    // 使用 OverlayGroup 保持同一组的容器，方便刷新和显隐
    let overlayGroup = this.overlayGroups[groupId]
    if (!overlayGroup) {
      overlayGroup = new this.AMap.OverlayGroup(markers)
      this.overlayGroups[groupId] = overlayGroup
      this.map.add(overlayGroup)
    } else {
      overlayGroup.clearOverlays()
      if (markers.length) {
        overlayGroup.addOverlays(markers)
      }
    }

    this.setOverlayGroupVisible(overlayGroup, visible)

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'marker',
      visible: visible,
      groupType: options.groupType || '点位',
      items: items,
      getDataFn: getDataFn, // 保存数据获取函数，用于后续更新
      onClick: onClick, // 保存点击事件回调，用于后续重新绑定
      isAutoRefresh: isAutoRefresh, // 是否定时刷新
      refreshInterval: refreshInterval, // 刷新间隔（秒）
      showLabel: showLabel, // 是否显示标签
      labelPosition: labelPosition // 标签位置
    }

    // 绑定组内标记点点击事件
    this.bindMarkerClickEvents(markers, items, onClick)

    // 如果启用定时刷新且图层可见，启动定时器
    if (isAutoRefresh && visible) {
      this.startMarkerRefresh(groupId)
    }

    return markers
  }

  /**
   * 绑定marker点击事件
   * @param {Array} markers marker数组
   * @param {Array} items 数据项数组
   * @param {Function} onClick 点击回调函数
   */
  bindMarkerClickEvents (markers, items, onClick) {
    if (typeof onClick === 'function') {
      markers.forEach((marker, idx) => {
        try {
          const info = { id: items[idx]?.id, name: items[idx]?.name }
          if (marker && typeof marker.setExtData === 'function') {
            marker.setExtData(info)
          }
          marker.on('click', (evt) => {
            try {
              const pos = marker.getPosition()
              const lng = typeof pos.getLng === 'function' ? pos.getLng() : pos.lng
              const lat = typeof pos.getLat === 'function' ? pos.getLat() : pos.lat
              onClick({ id: info.id, name: info.name, coordinate: [lng, lat], marker, event: evt }, items[idx])
            } catch (e) {
            }
          })
        } catch (e) {
        }
      })
    }
  }

  /**
   * 添加热力图
   *
   * @param {Function} getDataFn 获取热力图数据的函数，返回 Promise<Array<object>>
   *  返回的数据格式：[{ lng, lat, count }]
   * @param {object} options 组配置选项
   * @param {string} options.groupId 组数据唯一标识（必填）
   * @param {string} options.groupName 组数据名称（必填）
   * @param {boolean} [options.visible=true] 默认是否显示，默认 true
   * @returns {Promise<AMap.HeatMap>} 热力图实例
   */
  async addHeatmap (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 如果组已存在，先清理旧的热力图
    if (this.groups[groupId] && this.layers[groupId]) {
      const oldHeatmap = this.layers[groupId]
      if (oldHeatmap && typeof oldHeatmap.hide === 'function') {
        oldHeatmap.hide()
      }
      if (oldHeatmap && typeof oldHeatmap.setMap === 'function') {
        oldHeatmap.setMap(null)
      }
      delete this.layers[groupId]
    }

    // 调用数据获取函数获取最新数据
    const heatmapData = await getDataFn()

    // 确保 AMap.HeatMap 插件已加载
    if (!this.AMap.HeatMap) {
      throw new Error('AMap.HeatMap plugin is not loaded')
    }

    // 创建热力图实例
    const heatmap = new this.AMap.HeatMap(this.map, {
      radius: 35,
      opacity: [0, 0.85],
      gradient: {
        0.2: '#2cc0ff',
        0.4: '#02f0a0',
        0.7: '#ffb347',
        1.0: '#ff4d4f'
      }
    })

    // 设置热力图数据
    if (heatmapData && heatmapData.length > 0) {
      const max = Math.max(...heatmapData.map(item => item.count || 1))
      heatmap.setDataSet({
        data: heatmapData,
        max: max || 1
      })
    }

    // 设置显隐
    if (visible) {
      heatmap.show()
    } else {
      heatmap.hide()
    }

    // 保存到图层映射
    this.layers[groupId] = heatmap

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'heatmap',
      visible: visible,
      groupType: options.groupType || '热力图',
      items: heatmapData,
      getDataFn: getDataFn // 保存数据获取函数，用于后续更新
    }

    return heatmap
  }

  /**
   * 批量绘制多边形
   *
   * @param {Function} getDataFn - 获取多边形数据的函数，返回 Promise<Array<object>>
   * @param {object} options - 组配置选项
   * @param {string} options.groupId - 组数据唯一标识（必填）
   * @param {string} options.groupName - 组数据名称（必填）
   * @param {boolean} [options.visible=true] - 默认是否显示，默认 true
   * @returns {Promise<Array<AMap.Polygon>>} 多边形实例数组
   */
  async drawPolygon (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    // 调用数据获取函数获取最新数据
    const polygonOptionsList = await getDataFn()

    // 如果组已存在，先清理旧的多边形
    if (this.groups[groupId]) {
      const oldGroup = this.groups[groupId]
      if (oldGroup.items) {
        oldGroup.items.forEach((item) => {
          const polygon = this.layers[item.id]
          if (polygon) {
            this.map.remove(polygon)
            delete this.layers[item.id]
          }
        })
      }
    }

    const { polygons, items } = this.buildPolygons(polygonOptionsList)

    // 使用 OverlayGroup 保持同一组的容器，方便刷新和显隐
    let overlayGroup = this.overlayGroups[groupId]
    if (!overlayGroup) {
      overlayGroup = new this.AMap.OverlayGroup(polygons)
      this.overlayGroups[groupId] = overlayGroup
      this.map.add(overlayGroup)
    } else {
      overlayGroup.clearOverlays()
      if (polygons.length) {
        overlayGroup.addOverlays(polygons)
      }
    }

    this.setOverlayGroupVisible(overlayGroup, visible)

    // 创建或更新组数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'polygon',
      visible: visible,
      groupType: options.groupType || '片区',
      items: items,
      getDataFn: getDataFn // 保存数据获取函数，用于后续更新
    }

    return polygons
  }

  /**
   * 批量绘制折线
   * @param {Function} getDataFn 获取数据函数
   * @param {object} options 组配置
   */
  async addPolyline (getDataFn, options = {}) {
    if (!this.map) throw new Error('Map not initialized')

    const { groupId, groupName, visible = true } = options
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }

    const polylineOptions = await getDataFn()
    console.warn('>>>管线数据', polylineOptions)

    // 清理旧线
    if (this.groups[groupId]) {
      const oldGroup = this.groups[groupId]
      if (oldGroup.items) {
        oldGroup.items.forEach((item) => {
          const line = this.layers[item.id]
          if (line) {
            this.map.remove(line)
            delete this.layers[item.id]
          }
        })
      }
    }

    const { polylines, items } = this.buildPolylines(polylineOptions)

    let og = this.overlayGroups[groupId]
    if (!og) {
      og = new this.AMap.OverlayGroup(polylines)
      this.overlayGroups[groupId] = og
      this.map.add(og)
    } else {
      og.clearOverlays()
      if (polylines.length) {
        og.addOverlays(polylines)
      }
    }
    this.setOverlayGroupVisible(og, visible)

    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'polyline',
      visible,
      groupType: options.groupType || '线',
      items,
      getDataFn
    }
    return polylines
  }

  /**
   * 添加单个 WMS 图层
   *
   * 注意：高德地图原生不支持 WMS 标准协议
   * 此处通过官方 WMS 瓦片图层能力实现
   *
   * 性能优化说明：
   * 1. 默认不添加时间戳参数，允许浏览器缓存瓦片，减少重复请求
   * 2. 使用 PNG8 格式（比 PNG24 文件更小，加载更快）
   * 3. 限制缩放级别范围，避免在低级别加载过多瓦片
   * 4. 标准瓦片大小 256x256，平衡性能和清晰度
   *
   * 进一步优化建议（服务端）：
   * - 在 GeoServer 中启用 GeoWebCache（GWC）瓦片缓存
   * - 配置合适的瓦片缓存策略和过期时间
   * - 使用 CDN 加速瓦片服务
   * - 优化 GeoServer 的渲染性能配置
   *
   * @param {object} layerOptions - WMS 图层配置对象
   * @param {string} layerOptions.id - 图层唯一标识（必填）
   * @param {string} layerOptions.name - 图层名称（必填）
   * @param {string} layerOptions.url - WMS 服务地址
   * @param {object} layerOptions.params - WMS 请求参数
   * @param {boolean} [layerOptions.visible=true] - 初始显隐
   * @param {Function} [layerOptions.getLayerVersion] - 可选：外部传入的版本号获取函数
   *  约定签名：(tableName: string) => (Promise<number|string>|number|string)
   * @param {Array<number>} [layerOptions.zooms=[3,18]] - 缩放级别范围
   * @param {number} [layerOptions.tileSize=256] - 瓦片大小
   * @param {number} [layerOptions.opacity=1] - 图层透明度
   * @param {string} groupId - 组数据唯一标识（必填）
   * @param {string} groupName - 组数据名称（必填）
   * @param {string} [groupType='wms'] - 组类型标识
   * @returns {Promise<TileLayer>} 图层实例数组（单元素）
   */
  async addWmsLayer (layerOptions, groupId, groupName, groupType = 'wms') {
    if (!this.map) throw new Error('Map not initialized')
    if (!groupId || !groupName) {
      throw new Error('groupId and groupName are required')
    }
    if (!layerOptions || !layerOptions.id || !layerOptions.name) {
      throw new Error('layerOptions.id and layerOptions.name are required')
    }

    // 如果已有同 ID 的图层，先清理
    const exist = this.layers[layerOptions.id]
    if (exist) {
      exist.setMap(null)
      delete this.layers[layerOptions.id]
    }

    // 使用高德官方 WMS 图层能力
    // 性能优化配置：
    // 1. 移除时间戳参数，允许浏览器缓存瓦片（除非需要强制刷新）
    // 2. 设置合适的瓦片大小（256x256 是标准大小，性能较好）
    // 3. 限制缩放级别范围，避免在低级别加载过多瓦片
    // 4. 使用高效的图片格式（PNG8 或 JPEG）
    const wmsParams = {
      LAYERS: layerOptions.params.LAYERS,
      VERSION: layerOptions.params.VERSION || '1.3.0',
      FORMAT: layerOptions.params.FORMAT || 'image/png8', // 使用 PNG8 格式，文件更小
      TRANSPARENT: layerOptions.params.TRANSPARENT !== false ? 'true' : 'false',
      TILED: true, // 是否使用分块瓦片模式
      LAYERVERSION: String(layerOptions.params.LAYERVERSION)
    }
    // ✅ 工程化：版本号由 layerOptions.getLayerVersion 注入或兜底使用时间戳
    // - 有回调：使用回调返回的版本号（推荐）
    // - 无回调：使用 Date.now() 作为版本号，避免缓存
    try {
      const v = typeof layerOptions.getLayerVersion === 'function'
        ? await layerOptions.getLayerVersion(layerOptions.id)
        : Date.now()
      wmsParams.LAYERVERSION = String(v ?? Date.now())
    } catch (e) {
      // 回调失败时兜底时间戳，保证仍能刷新
      wmsParams.LAYERVERSION = String(Date.now())
    }

    const layer = new this.AMap.TileLayer.WMS({
      url: layerOptions.url,
      params: wmsParams,
      visible: layerOptions.visible,
      // 性能优化：设置瓦片大小（默认256，保持标准大小）
      tileSize: layerOptions.tileSize || 256,
      // 性能优化：限制缩放级别范围，避免在低级别加载过多瓦片
      // 根据实际需求调整，例如只在高缩放级别显示
      zooms: layerOptions.zooms || [3, 18],
      // 性能优化：设置透明度，减少渲染负担
      opacity: layerOptions.opacity !== undefined ? layerOptions.opacity : 1,
      // 性能优化：设置图层层级
      zIndex: layerOptions.zIndex || 100
    })

    layer.setMap(this.map)
    this.layers[layerOptions.id] = layer

    // 创建或更新数据
    this.groups[groupId] = {
      id: groupId,
      name: groupName,
      type: 'wms',
      groupType: groupType || 'wms',
      visible: layerOptions.visible === true,
      items: [],
      // 保存WMS配置信息，用于GetFeatureInfo查询
      wmsConfig: {
        url: layerOptions.url,
        params: layerOptions.params,
        // 缓存版本号回调，供 refreshWmsLayers 直接调用（无需额外传参）
        getLayerVersion: layerOptions.getLayerVersion
      }
    }

    return layer
  }

  /**
   * 刷新当前显示的WMS图层（强制刷新缓存）
   * key: 图层表名称 数组, 只刷新指定图层
   */
  async refreshWmsLayers (key = null) {
    if (!this.map) { return }

    try {
      // 1️⃣ 取出所有 WMS 分组（顺序非常关键）
      let wmsGroups = Object.values(this.groups)
        .filter(g => g.type === 'wms')

      if (key && Array.isArray(key)) {
        wmsGroups = wmsGroups.filter(g => key.includes(g.id))
      }

      if (!wmsGroups.length) { return }

      // 2️⃣ 记录刷新前的顺序 + 显隐状态 + WMS 配置
      const snapshot = wmsGroups.map(group => {
        return {
          groupId: group.id,
          groupName: group.name,
          groupType: group.groupType,
          visible: group.visible === true,
          layerOptions: {
            id: group.id,
            name: group.name,
            url: group.wmsConfig.url,
            params: group.wmsConfig.params,
            visible: group.visible === true,
            getLayerVersion: group.wmsConfig.getLayerVersion
          }
        }
      })

      // 3️⃣ 统一移除所有 WMS（不区分显隐）
      for (const item of snapshot) {
        await this.clearLayer(item.groupId)
      }

      // 4️⃣ 按原始顺序重新创建（层级在这里被“锁死”）
      for (const item of snapshot) {
        const { layerOptions, groupId, groupName, groupType } = item

        // ⚠️ addWmsLayer 内部已经 setMap
        await this.addWmsLayer(
          layerOptions,
          groupId,
          groupName,
          groupType
        )

        // 5️⃣ 如果之前是隐藏的，补一刀关掉
        if (!item.visible) {
          const layer = this.layers[groupId]
          if (layer) { layer.setMap(null) }
        }
      }
    } catch (e) {
      console.error('>>>> 强制刷新 WMS 图层失败', e)
    }
  }

  /**
   * 清理图层
   *
   * @param {string} [layerId] - 要清理的图层 ID；不传则清理所有业务图层
   * @returns {Promise<void>}
   */
  async clearLayer (layerId) {
    if (!this.map) return

    // 如果指定了图层 ID，只清理该图层（layerId 可以是单个覆盖物 ID 或组 ID）
    if (layerId) {
      // 优先按组 OverlayGroup 清理
      const og = this.overlayGroups[layerId]
      if (og) {
        this.map.remove(og)
        og.clearOverlays()
        delete this.overlayGroups[layerId]
        if (this.groups[layerId]) {
          delete this.groups[layerId]
        }
        return
      }

      const target = this.layers[layerId]
      if (target) {
        // 如果是热力图，需要特殊处理
        if (this.groups[layerId] && this.groups[layerId].type === 'heatmap') {
          if (target && typeof target.hide === 'function') {
            target.hide()
          }
          if (target && typeof target.setMap === 'function') {
            target.setMap(null)
          }
        } else {
          this.map.remove(target) // 从地图移除
        }
        delete this.layers[layerId] // 从映射表删除
      }

      // 如果是组 ID，同时清理组数据
      if (this.groups[layerId]) {
        // 清理组内所有覆盖物
        const group = this.groups[layerId]
        if (group.items) {
          group.items.forEach((item) => {
            const overlay = this.layers[item.id]
            if (overlay) {
              this.map.remove(overlay)
              delete this.layers[item.id]
            }
          })
        }
        delete this.groups[layerId]
      }

      return
    }

    // 未指定 ID 时，清理所有业务图层
    Object.values(this.overlayGroups).forEach((og) => {
      this.map.remove(og)
      og.clearOverlays()
    })
    this.overlayGroups = {}

    Object.values(this.layers).forEach((layer) => {
      this.map.remove(layer)
    })
    this.layers = {}
    this.groups = {}
  }

  /**
   * 切换底图类型
   *
   * 高德地图支持的底图类型：
   * - 'gaode' / 'gaodeVec': 高德矢量地图（默认）
   * - 'gaodeSat': 高德卫星地图 + 路网标注
   *
   * 注意：高德地图不支持天地图底图
   *
   * @param {string} mapType - 底图类型，默认 'gaodeVec'
   * @returns {void}
   */
  switchBaseMap (mapType) {
    if (!this.map) return

    if (mapType === 'gaodeSat') {
      this.satelliteLayer.setMap(this.map)
      this.roadNetLayer.setMap(this.map)
      return
    }

    // ✅ 切回矢量：不动 vec，只移除覆盖层
    this.satelliteLayer.setMap(null)
    this.roadNetLayer.setMap(null)

    // ✅ 保留 3D / pitch / buildings
    this.map.setMapStyle('amap://styles/normal')
  }

  // 初始化时 卫星影像地图
  initSatelliteLayers () {
    this.satelliteLayer = new this.AMap.TileLayer.Satellite()
    this.roadNetLayer = new this.AMap.TileLayer.RoadNet()
  }

  /**
   * 设置组数据显隐状态
   *
   * @param {string} groupId - 组数据唯一标识
   * @param {boolean} visible - 是否显示
   * @returns {Promise<void>}
   */
  async setGroupVisible (groupId, visible) {
    if (!this.map) return

    const group = this.groups[groupId]
    if (!group) {
      console.warn(`Group ${groupId} not found`)
      return
    }
    // WMS 图层
    if (group.type === 'wms') {
      const layer = this.layers[groupId]
      if (layer) {
        // 高德 通过 setMap 方法从地图添加/移除图层
        // layer.setMap(visible ? this.map : null)
        // 切换图层的可见性属性
        visible ? layer.show() : layer.hide()
      }
      group.visible = visible
      return
    }
    // 热力图
    if (group.type === 'heatmap') {
      const heatmap = this.layers[groupId]
      if (heatmap) {
        visible ? heatmap.show() : heatmap.hide()
      }
      group.visible = visible
      return
    }
    // 标记点类型：控制定时刷新
    if (group.type === 'marker') {
      if (visible) {
        // 显示时：先停止定时器，获取最新数据并更新，再启动定时器
        this.stopMarkerRefresh(groupId)
        if (group.getDataFn) {
          await this.refreshMarkerData(groupId)
        }
        // 如果启用定时刷新，启动定时器
        if (group.isAutoRefresh) {
          this.startMarkerRefresh(groupId)
        }
      } else {
        // 隐藏时：停止定时刷新
        this.stopMarkerRefresh(groupId)
      }
      // 更新显隐状态
      group.visible = visible
      const og = this.overlayGroups[groupId]
      if (og) {
        this.setOverlayGroupVisible(og, visible)
      }
      return
    }
    // 如果设置为显示且有数据函数：重新获取数据后更新覆盖物（保持同一组容器）
    if (visible && group.getDataFn) {
      const data = await group.getDataFn()

      let items = []
      if (group.type === 'polygon') {
        const { polygons, items: nextItems } = this.buildPolygons(data)
        const og = this.ensureOverlayGroup(groupId, polygons)
        this.setOverlayGroupVisible(og, true)
        items = nextItems
      } else if (group.type === 'polyline') {
        const { polylines, items: nextItems } = this.buildPolylines(data)
        const og = this.ensureOverlayGroup(groupId, polylines)
        this.setOverlayGroupVisible(og, true)
        items = nextItems
      } else if (group.type === 'heatmap') {
        const heatmap = this.layers[groupId]
        if (heatmap && data && data.length > 0) {
          const max = Math.max(...data.map(item => item.count || 1))
          heatmap.setDataSet({
            data: data,
            max: max || 1
          })
        }
        items = data
      }

      group.items = items
      group.visible = true
      return
    }

    // 更新组数据显隐状态
    group.visible = visible

    // 性能优化：批量控制组内所有覆盖物的显隐
    // 优先用 OverlayGroup 控制
    const og = this.overlayGroups[groupId]
    if (og) {
      this.setOverlayGroupVisible(og, visible)
      // return
    }
  }

  /**
   * 启动标记点定时刷新
   * @param {string} groupId - 组 ID
   * @private
   */
  startMarkerRefresh (groupId) {
    const group = this.groups[groupId]
    if (!group || !group.isAutoRefresh || !group.getDataFn) {
      return
    }

    // 如果已有定时器，先清除
    this.stopMarkerRefresh(groupId)

    const interval = (group.refreshInterval || 10) * 1000 // 转换为毫秒
    const timer = setInterval(async () => {
      // 只有在图层可见时才刷新
      if (group.visible) {
        await this.refreshMarkerData(groupId)
      }
    }, interval)

    this.refreshTimers[groupId] = timer
  }

  /**
   * 停止标记点定时刷新
   * @param {string} groupId - 组 ID
   * @private
   */
  stopMarkerRefresh (groupId) {
    const timer = this.refreshTimers[groupId]
    if (timer) {
      clearInterval(timer)
      delete this.refreshTimers[groupId]
    }
  }

  /**
   * 刷新标记点数据
   * @param {string} groupId - 组 ID
   * @private
   */
  async refreshMarkerData (groupId) {
    const group = this.groups[groupId]
    if (!group || !group.getDataFn || !this.map) {
      return
    }

    try {
      // 获取最新数据
      const markerOptionsList = await group.getDataFn()

      // 清理旧的标记点和标签
      if (group.items) {
        group.items.forEach((item) => {
          const marker = this.layers[item.id]
          if (marker) {
            this.map.remove(marker)
            delete this.layers[item.id]
          }
        })
      }

      // 构建新的标记点
      const { markers, items } = this.buildMarkers(markerOptionsList, {
        showLabel: group.showLabel || false,
        labelPosition: group.labelPosition || 'bottom'
      })

      // 更新 OverlayGroup
      const overlayGroup = this.overlayGroups[groupId]
      if (overlayGroup) {
        overlayGroup.clearOverlays()
        if (markers.length) {
          overlayGroup.addOverlays(markers)
        }
      }

      // 更新组数据
      group.items = items

      // 重新绑定点击事件
      this.bindMarkerClickEvents(markers, items, group.onClick)
    } catch (error) {
      console.error(`刷新标记点数据失败，组 ID: ${groupId}`, error)
    }
  }

  /**
   * 开启组闪烁（通过显隐切换）
   */
  startGroupBlink (groupId, interval = 600) {
    const og = this.overlayGroups[groupId]
    if (!og) return
    this.stopGroupBlink(groupId)
    let visible = true
    this.blinkTimers[groupId] = setInterval(() => {
      visible = !visible
      this.setOverlayGroupVisible(og, visible)
    }, interval)
  }

  /**
   * 停止组闪烁并恢复显示
   */
  stopGroupBlink (groupId) {
    const timer = this.blinkTimers[groupId]
    if (timer) {
      clearInterval(timer)
      delete this.blinkTimers[groupId]
    }
    const og = this.overlayGroups[groupId]
    if (og) {
      this.setOverlayGroupVisible(og, true)
    }
  }

  /**
   * 获取所有组数据信息
   *
   * @returns {Promise<Array<Object>>} 组数据信息数组
   */
  getGroups () {
    const groups = []
    Object.values(this.groups).forEach((group) => {
      groups.push({
        id: group.id,
        name: group.name,
        type: group.type,
        groupType: group.groupType,
        visible: group.visible,
        count: group.items.length
      })
    })
    return groups
  }

  /**
   * 根据 ID 获取组数据信息
   *
   * @param {string} groupId - 组数据唯一标识
   * @returns {Object|null} 组数据信息对象，如果不存在则返回 null
   */
  getGroupById (groupId) {
    return this.groups[groupId] || null
  }

  /**
   * 根据 type 获取图层组，如果不传 type 则返回所有组
   * @param {string} [type] - 可选，要过滤的图层类型 (如 'marker', 'polygon')
   * @returns {Array<Object>} 过滤后的图层组数组
   */
  getGroupsByType (type) {
    // 先获取所有组的统一格式数据
    const allGroups = this.getGroups()

    // 如果传入了 type 参数，则进行过滤
    if (type) {
      return allGroups.filter(group => group.type === type)
    }

    // 如果没有传参数，直接返回所有组
    return allGroups
  }

  /**
   * 将图层组按 groupType 分类，或获取指定分类的图层组
   * @param {string} [specificGroupType] - 可选，指定要获取的 groupType
   * @returns {Array<Object>|Array<{groupType: string, groups: Array<Object>}>}
   *          如果传入了 specificGroupType，返回该分类的组数组；
   *          否则，返回所有分类的对象数组。
   */
  getGroupsByGroupType (specificGroupType) {
    // 先获取一维数组
    const allGroups = this.getGroups()

    // 使用 reduce 进行分组
    const grouped = allGroups.reduce((accumulator, currentGroup) => {
      const existingCategory = accumulator.find(item => item.groupType === currentGroup.groupType)

      if (existingCategory) {
        existingCategory.groups.push(currentGroup)
      } else {
        accumulator.push({
          groupType: currentGroup.groupType,
          groups: [currentGroup]
        })
      }

      return accumulator
    }, [])

    // 如果传入了 specificGroupType 参数
    if (specificGroupType) {
      // 在分组结果中查找对应的分类
      const foundCategory = grouped.find(item => item.groupType === specificGroupType)
      // 如果找到了，返回它的 groups 数组；如果没找到，返回空数组
      return foundCategory ? foundCategory.groups : []
    }

    // 如果没有传参数，返回完整的分组结果
    return grouped
  }

  // ===== 私有方法 =====

  /**
   * 确保高德地图 SDK 已加载
   *
   * 实现懒加载策略：
   * 1. 如果已加载过，直接返回缓存的 AMap 对象
   * 2. 如果全局 window.AMap 存在（外部已加载），直接复用
   * 3. 否则通过 AMapLoader 异步加载 SDK
   *
   * @returns {Promise<object>} 高德地图 SDK 命名空间对象
   * @private
   */
  ensureSdkLoaded (key) {
    // 如果已加载或全局存在，直接返回
    if (this.AMap || window.AMap) {
      // 兼容场景：如果外部已全局注入 AMap，则直接复用，避免重复加载
      this.AMap = this.AMap || window.AMap
      return Promise.resolve(this.AMap)
    }

    // 使用 AMapLoader 异步加载 SDK
    return AMapLoader.load({
      key: key, // 高德地图 key
      version: '2.0', // SDK 版本
      plugins: ['AMap.IndexCluster', 'AMP.MarkerCluster', 'AMap.InfoWindow', 'AMap.HeatMap', 'AMap.HawkEye', 'AMap.DistrictSearch',
        'AMap.ToolBar', 'AMap.Geolocation', 'AMap.MouseTool',
        'AMap.Geocoder', 'AMap.MarkerClusterer', 'AMap.AutoComplete', 'AMap.Scale', 'AMap.RangingTool'], // 需要加载的插件
    }).then((AMap) => {
      // 缓存加载后的 AMap 对象
      this.AMap = AMap
      return AMap
    })
  }

  /**
   * 获取地图当前状态
   * 用于切换引擎时保存状态
   *
   * @returns {object} 地图状态对象
   */
  getMapState () {
    if (!this.map) return null

    const center = this.map.getCenter()
    const zoom = this.map.getZoom()

    return {
      center: center ? [center.lng, center.lat] : null,
      zoom: zoom || null,
    }
  }

  /**
   * 销毁地图实例
   * 用于切换引擎时清理资源
   */
  destroy () {
    // 清理所有定时刷新器
    Object.keys(this.refreshTimers).forEach(groupId => {
      this.stopMarkerRefresh(groupId)
    })
    this.refreshTimers = {}

    if (this.map) {
      this.map.destroy()
      this.map = null
    }
    this.clearMeasureResults()
    this.layers = {}
    this.groups = {}
  }

  // ===== 私有工具 =====

  buildMarkers (markerOptionsList, labelOptions = {}) {
    const markers = []
    const items = []
    const { showLabel = false, labelPosition = 'bottom' } = labelOptions

    for (const markerOptions of markerOptionsList) {
      if (!markerOptions.id || !markerOptions.name) {
        throw new Error('Each marker must have id and name')
      }
      const imageSize = markerOptions.imageSize || [30, 30]
      const size = markerOptions.size || [30, 30]
      const anchor = markerOptions.anchor || 'bottom-center'
      const marker = new this.AMap.Marker({
        position: [markerOptions.lng, markerOptions.lat],
        icon: new this.AMap.Icon({
          image: markerOptions.icon,
          size: new this.AMap.Size(size[0], size[1]), // 设置图标大小
          imageSize: new this.AMap.Size(imageSize[0], imageSize[1]) // 设置图片显示大小
        }),
        anchor: anchor // 设置锚点为底部中间,
      })

      this.layers[markerOptions.id] = marker

      // 如果需要显示标签，创建文本标签
      if (showLabel && markerOptions.name) {
        marker.setLabel({
          direction: labelPosition,
          content: `<div class='info' style="background: #ffffff; padding: 3px; font-size: 1em; line-height: 1.2em">${markerOptions.name}</div>`, // 设置文本标注内容
        })
      }

      markers.push(marker)
      items.push({
        ...markerOptions
      })
    }

    return { markers, items }
  }

  buildPolygons (polygonOptionsList) {
    const polygons = []
    const items = []

    for (const polygonOptions of polygonOptionsList) {
      if (!polygonOptions.id || !polygonOptions.name) {
        throw new Error('Each polygon must have id and name')
      }

      const polygon = new this.AMap.Polygon({
        path: polygonOptions.points,
        strokeColor: polygonOptions.strokeColor || '#1890ff',
        fillColor: polygonOptions.fillColor || 'rgba(24,144,255,0.3)',
      })

      this.layers[polygonOptions.id] = polygon

      polygons.push(polygon)
      items.push({
        id: polygonOptions.id,
        name: polygonOptions.name,
      })
    }

    return { polygons, items }
  }

  buildPolylines (polylineOptionsList) {
    const polylines = []
    const items = []

    for (const lineOptions of polylineOptionsList) {
      if (!lineOptions.id || !lineOptions.name || !Array.isArray(lineOptions.points)) {
        throw new Error('Each polyline must have id, name and points')
      }

      const polyline = new this.AMap.Polyline({
        path: lineOptions.points,
        strokeColor: lineOptions.strokeColor || '#ff0000',
        strokeWeight: lineOptions.strokeWidth || 6,
        showDir: false
      })

      this.layers[lineOptions.id] = polyline
      polylines.push(polyline)
      items.push({
        id: lineOptions.id,
        name: lineOptions.name
      })
    }

    return { polylines, items }
  }

  /**
   * 确保组 OverlayGroup 存在并用新覆盖物刷新
   * 保留同一容器，避免闪烁
   */
  ensureOverlayGroup (groupId, overlays) {
    let og = this.overlayGroups[groupId]
    if (!og) {
      og = new this.AMap.OverlayGroup(overlays)
      this.overlayGroups[groupId] = og
      this.map.add(og)
    } else {
      og.clearOverlays()
      if (overlays && overlays.length) {
        og.addOverlays(overlays)
      }
    }
    return og
  }

  /**
   * 设置组 OverlayGroup 显隐状态
   */
  setOverlayGroupVisible (og, visible) {
    if (!og) return
    if (visible) {
      og.show()
    } else {
      og.hide()
    }
  }

  /**
   * 地图放大
   */
  zoomIn () {
    if (!this.map) return
    this.map.zoomIn()
  }

  /**
   * 地图缩小
   */
  zoomOut () {
    if (!this.map) return
    this.map.zoomOut()
  }

  /**
   * 动态计算平移安全 zoom
   * @param {Array} from - [lng, lat]
   * @param {Array} to - [lng, lat]
   * @param {AMap.Map} map - 地图实例
   * @returns {number} SAFE_ZOOM
   */
  calcSafeZoom (from, to, map) {
    const container = map.getContainer()
    const width = container.offsetWidth
    const height = container.offsetHeight
    const MAX_ZOOM = map.getZooms()[1] || 18

    const [x1, y1] = this.coord4326To3857(from[0], from[1])
    const [x2, y2] = this.coord4326To3857(to[0], to[1])

    const dx = Math.abs(x2 - x1)
    const dy = Math.abs(y2 - y1)

    // 公式：container 宽高 / 米跨度 * 每 tile 256 px
    const zoomX = Math.log2(width * 20037508.34 * 2 / (dx * 256))
    const zoomY = Math.log2(height * 20037508.34 / (dy * 256))

    const safeZoom = Math.floor(Math.min(zoomX, zoomY, MAX_ZOOM))
    return safeZoom
  }

  /**
   * 设置地图视图（智能版）
   * 大 zoom + 长距离：先缩小 zoom → 平移 → 恢复 zoom
   * 小距离：直接 setZoomAndCenter
   *
   * @param {number} zoom - 缩放级别
   * @param {Array} center - [lng, lat]
   */
  fitView (zoom = null, center = null) {
    try {
      if (!this.map) return

      const map = this.map
      const mapState = this.getMapState()

      if (!center) {
        center = mapState.center
      }
      if (!zoom) {
        zoom = mapState.zoom
      }

      const currentZoom = mapState.zoom
      const currentCenter = mapState.center

      // ====== 判断平移距离 ======
      const lngDiff = Math.abs(currentCenter[0] - center[0])
      const latDiff = Math.abs(currentCenter[1] - center[1])
      const distance = lngDiff + latDiff

      // ====== 阈值配置 ======
      const ZOOM_THRESHOLD = this.calcSafeZoom(currentCenter, center, map)
      const DIST_THRESHOLD = 1 // 认为是“长距离”的阈值
      const SAFE_ZOOM = 6 // 平移时的安全 zoom
      const ANIM_TIME = 300 // 动画时间（ms）

      window.console.log('>>distance ', distance, '>>ZOOM_THRESHOLD ', ZOOM_THRESHOLD)
      window.console.log('>>currentZoom ', currentZoom, '>>DIST_THRESHOLD ', DIST_THRESHOLD)
      // ====== 小距离 or zoom 不大：直接跳 ======
      if (currentZoom < ZOOM_THRESHOLD || distance < DIST_THRESHOLD) {
        map.setZoomAndCenter(zoom, center)
        return
      }

      // ====== 大 zoom + 长距离方案 ======
      // 1️⃣ 先缩小 zoom
      map.setZoom(SAFE_ZOOM, false)

      setTimeout(() => {
        // 2️⃣ 平移
        map.setCenter(center, false)

        setTimeout(() => {
          // 3️⃣ 放大到目标 zoom
          map.setZoom(zoom, false)
        }, ANIM_TIME)
      }, ANIM_TIME)
    } catch (e) {
      console.error('>> fitView error ', e)
    }
  }

  /**
   * 启用框选放大功能
   */
  enableZoomToBox () {
    if (!this.map) return
    if (!this.AMap) {
      console.warn('AMap SDK not loaded')
      return
    }
    this.disableZoomToBox()
    if (!this.mouseTool) {
      this.mouseTool = new this.AMap.MouseTool(this.map)
    }

    // 启用框选放大
    this.mouseTool.rectZoomIn({
      strokeColor: '#1890ff',
      strokeWeight: 2,
      strokeOpacity: 0.8,
      fillColor: 'rgba(24,144,255,0.1)',
      fillOpacity: 0.3
    })
  }

  /**
   * 禁用框选放大功能
   */
  disableZoomToBox () {
    if (this.mouseTool) {
      this.mouseTool.close(true)
      // 在 AMap 中，MouseTool 可能会影响地图的默认交互，需要显式恢复
      if (this.map) {
        // 恢复地图的默认交互状态
        this.map.setStatus({ dragEnable: true })
      }
    }
  }

  /**
   * 启动距离测量（可多次）
   * @param {Function} onResultChange - (results: Array<{id, distance, color}>) => void
   */
  startDistanceMeasure (onResultChange) {
    if (!this.map) return
    if (!this.AMap) {
      console.warn('AMap SDK not loaded')
      return
    }

    this.measureResultCb = onResultChange

    if (!this.mouseTool) {
      this.mouseTool = new this.AMap.MouseTool(this.map)
    }

    // 移除旧的测距事件，避免重复
    if (this.measureDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.measureDrawHandler)
    }

    const options = {
      startMarkerOptions: {
        icon: new this.AMap.Icon({
          size: new this.AMap.Size(19, 31),
          imageSize: new this.AMap.Size(19, 31),
          image: '//webapi.amap.com/theme/v1.3/markers/b/start.png'
        }),
        offset: new this.AMap.Pixel(-9, -31)
      },
      midMarkerOptions: {
        icon: new this.AMap.Icon({
          size: new this.AMap.Size(19, 31),
          imageSize: new this.AMap.Size(19, 31),
          image: '//webapi.amap.com/theme/v1.3/markers/b/mid.png'
        }),
        offset: new this.AMap.Pixel(-9, -31)
      },
      endMarkerOptions: {
        icon: new this.AMap.Icon({
          size: new this.AMap.Size(19, 31),
          imageSize: new this.AMap.Size(19, 31),
          image: '//webapi.amap.com/theme/v1.3/markers/b/end.png'
        }),
        offset: new this.AMap.Pixel(-9, -31)
      },
      lineOptions: {
        strokeStyle: 'solid',
        strokeColor: '#47C9EF',
        strokeOpacity: 1,
        strokeWeight: 5
      }
    }

    this.measureDrawHandler = (e) => {
      const line = e?.obj
      let distance = 0
      if (line && line.getLength) {
        distance = Number((line.getLength() || 0).toFixed(2))
      }
      const result = {
        id: `measure-${Date.now()}-${this.measureResults.length + 1}`,
        distance,
        color: '#FF33FF'
      }
      this.measureResults.push(result)
      if (typeof this.measureResultCb === 'function') {
        this.measureResultCb([...this.measureResults])
      }
    }

    if (this.mouseTool.on) {
      this.mouseTool.on('draw', this.measureDrawHandler)
    }

    // 开启测距
    this.mouseTool.rule(options)
  }

  /**
   * 清空测量结果并退出测量模式
   */
  clearMeasureResults () {
    if (this.mouseTool) {
      // 移除事件并关闭工具，清理已有覆盖物
      if (this.measureDrawHandler && this.mouseTool.off) {
        this.mouseTool.off('draw', this.measureDrawHandler)
      }
      if (this.areaDrawHandler && this.mouseTool.off) {
        this.mouseTool.off('draw', this.areaDrawHandler)
      }
      this.measureDrawHandler = null
      this.areaDrawHandler = null
      this.mouseTool.close(true)
    }
    this.measureResults = []
    if (typeof this.measureResultCb === 'function') {
      this.measureResultCb([])
    }
  }

  /**
   * 面积测量（官方 MouseTool.measureArea 示例）
   */
  startAreaMeasure () {
    if (!this.map || !this.AMap) return
    if (!this.mouseTool) {
      this.mouseTool = new this.AMap.MouseTool(this.map)
    }

    // 取消旧的面积监听
    if (this.areaDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.areaDrawHandler)
    }

    this.areaDrawHandler = null // 暂不回调面积结果

    const options = {
      strokeColor: '#FED151',
      fillColor: '#FEBD5E',
      fillOpacity: 0.4
    }

    this.mouseTool.measureArea(options)
  }

  /**
   * 切换地图 2D / 3D 视图
   * @param {'2D' | '3D'} mode
   */
  setMapViewMode (mode) {
    if (!this.map) return

    if (!mode) {
      mode = this.getMapViewMode()
    }

    if (mode !== '2D') {
      this.map.setPitch(0) // 取消俯仰
      this.map.setRotation(0) // 取消旋转（建议）
      this.currentViewMode = '2D'
    } else {
      this.map.setPitch(45) // 默认 3D 倾斜角
      this.currentViewMode = '3D'
    }
  }

  /**
   * 获取当前地图视图模式
   * @returns {'2D' | '3D'}
   */
  getMapViewMode () {
    return this.currentViewMode
  }

  /**
   * 停止当前选择交互
   */
  stopSelectInteraction () {
    if (this.mouseTool) {
      if (this.selectDrawHandler && this.mouseTool.off) {
        this.mouseTool.off('draw', this.selectDrawHandler)
      }
      this.selectDrawHandler = null
      this.mouseTool.close(true)
    }
  }

  startRectangleSelect (onComplete) {
    return this.startShapeSelect('rectangle', onComplete)
  }

  startCircleSelect (onComplete) {
    return this.startShapeSelect('circle', onComplete)
  }

  startPolygonSelect (onComplete) {
    return this.startShapeSelect('polygon', onComplete)
  }

  startShapeSelect (type, onComplete) {
    if (!this.map || !this.AMap) return
    if (!this.mouseTool) {
      this.mouseTool = new this.AMap.MouseTool(this.map)
    }

    // 清理旧的监听与绘制
    if (this.measureDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.measureDrawHandler)
    }
    if (this.areaDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.areaDrawHandler)
    }
    if (this.selectDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.selectDrawHandler)
    }
    this.mouseTool.close(true)

    this.selectDrawHandler = (e) => {
      const obj = e?.obj
      let coords = []
      if (obj) {
        if (typeof obj.getPath === 'function') {
          const path = obj.getPath() || []
          coords = path.map(p => [p.lng, p.lat])
        } else if (typeof obj.getCenter === 'function' && typeof obj.getRadius === 'function') {
          const center = obj.getCenter()
          const radius = obj.getRadius()
          if (center && radius) {
            coords = this.circleToCoords(center, radius)
          }
        }
      }
      if (typeof onComplete === 'function') {
        onComplete(coords)
      }
      // 缓存绘制产生的覆盖物，便于按类型清理
      if (obj) {
        if (!this.selectOverlays) {
          this.selectOverlays = { rectangle: [], circle: [], polygon: [] }
        }
        const key = type === 'rectangle' ? 'rectangle' : (type === 'circle' ? 'circle' : 'polygon')
        this.selectOverlays[key].push(obj)
        // 打标签，便于从地图遍历时识别
        if (typeof obj.setExtData === 'function') {
          try {
            obj.setExtData({ selectType: key })
          } catch (e) {
          }
        }
      }
      this.mouseTool.close(false)
    }

    if (this.mouseTool.on) {
      this.mouseTool.on('draw', this.selectDrawHandler)
    }

    if (type === 'rectangle') {
      this.mouseTool.rectangle()
    } else if (type === 'circle') {
      this.mouseTool.circle()
    } else {
      this.mouseTool.polygon()
    }
  }

  /**
   * 清理框选结果覆盖物（不再按 type 区分，始终清除所有绘制）
   */
  clearSelectResults () {
    console.warn('clearSelectResults (clear all select overlays)')
    if (!this.map) return

    // 停止当前选择交互，避免残留状态
    this.stopSelectInteraction()

    if (!this.selectOverlays) {
      this.selectOverlays = { rectangle: [], circle: [], polygon: [] }
    }

    const removeList = (list) => {
      if (!Array.isArray(list)) return
      list.forEach((ov) => {
        try {
          if (ov && typeof ov.setMap === 'function') {
            ov.setMap(null)
          } else if (ov && this.map && typeof this.map.remove === 'function') {
            this.map.remove(ov)
          }
        } catch (e) {
        }
      })
    }

    const removeByScan = () => {
      try {
        if (typeof this.map.getAllOverlays !== 'function') return
        const all = this.map.getAllOverlays('') || []
        all.forEach((ov) => {
          try {
            if (typeof ov.getExtData === 'function') {
              const ext = ov.getExtData()
              const extType = ext && ext.selectType ? ext.selectType : null
              if (extType) {
                if (typeof ov.setMap === 'function') ov.setMap(null)
                else if (typeof this.map.remove === 'function') this.map.remove(ov)
              }
            }
          } catch (e) {
          }
        })
      } catch (e) {
      }
    }

    // 清理所有已记录的覆盖物
    removeList(this.selectOverlays.rectangle)
    removeList(this.selectOverlays.circle)
    removeList(this.selectOverlays.polygon)
    // 扫描地图中带有 selectType 标记的覆盖物并移除（兜底）
    removeByScan()
    this.selectOverlays = { rectangle: [], circle: [], polygon: [] }
  }

  /**
   * 开启自由画线（LineString）
   */
  startLineDraw (onComplete) {
    if (!this.map || !this.AMap) return
    if (!this.mouseTool) {
      this.mouseTool = new this.AMap.MouseTool(this.map)
    }

    // 清理其他绘制监听
    if (this.measureDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.measureDrawHandler)
    }
    if (this.areaDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.areaDrawHandler)
    }
    if (this.selectDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.selectDrawHandler)
    }
    if (this.lineDrawHandler && this.mouseTool.off) {
      this.mouseTool.off('draw', this.lineDrawHandler)
    }

    this.mouseTool.close(true)

    this.lineDrawHandler = (e) => {
      const obj = e?.obj
      const path = obj && typeof obj.getPath === 'function' ? obj.getPath() : []
      const coords = path ? path.map(p => [p.lng, p.lat]) : []
      if (typeof onComplete === 'function') {
        onComplete(coords)
      }
      this.stopLineDraw()
    }

    if (this.mouseTool.on) {
      this.mouseTool.on('draw', this.lineDrawHandler)
    }

    this.mouseTool.polyline({
      strokeColor: '#ff6600',
      strokeWeight: 4
    })
  }

  /**
   * 停止自由画线
   */
  stopLineDraw () {
    if (this.mouseTool) {
      if (this.lineDrawHandler && this.mouseTool.off) {
        this.mouseTool.off('draw', this.lineDrawHandler)
      }
      this.lineDrawHandler = null
      this.mouseTool.close(true)
    }
  }

  /**
   * 开启单条直线绘制（目前仅用于切面分析）
   * 只能绘制一条直线（两个点），绘制完成后自动结束交互
   */
  /**
   * 开始“单条直线”绘制（点击开始，第二次点击结束）
   * @param {Function} onComplete 结束回调，参数 [[lng, lat], [lng, lat]]
   */
  startStraightLineDraw (onComplete) {
    if (!this.map || !this.AMap) return

    // 初始化状态
    this._straightLineStart = null // 起点坐标
    this._straightLinePolyline = null // 预览线

    // 清理旧监听（防止多次绑定）
    this.stopStraightLineDraw()

    // 点击事件
    this._straightLineClickHandler = (e) => {
      const lngLat = e.lnglat
      const point = [lngLat.lng, lngLat.lat]

      // 第一次点击：开始
      if (!this._straightLineStart) {
        this._straightLineStart = point

        // 创建预览线
        this._straightLinePolyline = new this.AMap.Polyline({
          path: [point, point],
          strokeColor: '#ff6600',
          strokeWeight: 4,
          strokeOpacity: 1,
          map: this.map
        })

        return
      }

      // 第二次点击：结束
      const result = [
        this._straightLineStart,
        point
      ]

      if (typeof onComplete === 'function') {
        onComplete(result)
      }

      // 结束绘制
      this.stopStraightLineDraw()
    }

    // 鼠标移动事件（仅在已点击第一次后生效）
    this._straightLineMoveHandler = (e) => {
      if (!this._straightLineStart || !this._straightLinePolyline) return

      const lngLat = e.lnglat
      const movePoint = [lngLat.lng, lngLat.lat]

      this._straightLinePolyline.setPath([
        this._straightLineStart,
        movePoint
      ])
    }

    // 绑定事件
    this.map.on('click', this._straightLineClickHandler)
    this.map.on('mousemove', this._straightLineMoveHandler)
  }

  /**
   * 停止单条直线绘制
   */
  stopStraightLineDraw () {
    if (!this.map) return

    // 解绑事件
    if (this._straightLineClickHandler) {
      this.map.off('click', this._straightLineClickHandler)
      this._straightLineClickHandler = null
    }

    if (this._straightLineMoveHandler) {
      this.map.off('mousemove', this._straightLineMoveHandler)
      this._straightLineMoveHandler = null
    }

    // 清理线
    if (this._straightLinePolyline) {
      this._straightLinePolyline.setMap(null)
      this._straightLinePolyline = null
    }

    // 重置状态
    this._straightLineStart = null
  }

  /**
   * 将圆形转换为近似坐标数组
   * @param {AMap.LngLat} center
   * @param {number} radius - 单位米
   */
  circleToCoords (center, radius) {
    const step = 72
    const coords = []
    const radLat = center.lat * Math.PI / 180
    for (let i = 0; i < step; i++) {
      const angle = (2 * Math.PI * i) / step
      const dx = radius * Math.cos(angle)
      const dy = radius * Math.sin(angle)
      const lng = center.lng + (dx / (111320 * Math.cos(radLat)))
      const lat = center.lat + (dy / 110540)
      coords.push([lng, lat])
    }
    // 闭合
    coords.push(coords[0])
    return coords
  }

  /**
   * 当前定位
   * 使用高德地图 Geolocation 插件获取用户当前位置并定位到地图中心
   * @returns {Promise<{longitude: number, latitude: number}>} 返回定位坐标 {longitude, latitude}
   */
  async locateCurrent () {
    if (!this.map) {
      throw new Error('Map not initialized')
    }

    // 确保 SDK 已加载
    await this.ensureSdkLoaded()

    if (!this.AMap || !this.AMap.Geolocation) {
      throw new Error('AMap.Geolocation plugin not loaded')
    }

    return new Promise((resolve, reject) => {
      // 创建定位实例
      const geolocation = new this.AMap.Geolocation({
        enableHighAccuracy: true, // 是否使用高精度定位，默认:true
        timeout: 10000, // 超过10秒后停止定位，默认：无穷大
        maximumAge: 0, // 定位结果缓存0毫秒，默认：0
        convert: true, // 自动偏移坐标，偏移后的坐标为高德坐标，默认：true
        showButton: false, // 显示定位按钮，默认：true
        buttonDom: '', // 定位按钮的停靠位置，默认：'LB'
        buttonOffset: new this.AMap.Pixel(10, 20), // 定位按钮与设置的停靠位置的偏移量，默认：Pixel(10, 20)
        showMarker: false, // 定位成功后在定位到的位置显示点标记，默认：true
        showCircle: false, // 定位成功后用圆圈表示定位精度范围，默认：true
        panToLocation: false, // 定位成功后将定位到的位置作为地图中心点，默认：true（我们手动控制）
        zoomToAccuracy: false // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见，默认：false
      })

      // 定位成功回调
      geolocation.on('complete', (data) => {
        // 高德地图 Geolocation 返回的 position 是 AMap.LngLat 对象，包含 lng 和 lat 属性
        const position = data.position
        const longitude = position.lng || position.longitude
        const latitude = position.lat || position.latitude
        // 只返回坐标，不进行地图移动操作
        resolve({ longitude, latitude })
      })

      // 定位失败回调
      geolocation.on('error', (error) => {
        let errorMessage = '定位失败，请检查浏览器定位权限和网络连接'
        if (error.code === 1) {
          errorMessage = '定位失败：浏览器定位权限被拒绝。请在浏览器设置中允许此网站访问您的位置信息，然后重试。'
        } else if (error.code === 2) {
          errorMessage = '定位失败：无法获取位置信息，请检查设备定位功能是否开启'
        } else if (error.code === 3) {
          errorMessage = '定位超时，请重试'
        }
        reject(new Error(errorMessage))
      })

      // 开始定位
      geolocation.getCurrentPosition()
    })
  }

  /**
   * 打开信息窗口
   * @param {Array<number>} coordinate - 坐标 [lng, lat]
   * @param {HTMLElement} content - 窗口内容（DOM元素）
   *  注意：Vue 组件在调用层（如 index.vue）中已挂载为 DOM 元素，适配器只接收 DOM 元素
   * @param {object} [options] - 可选配置
   * @param {boolean} [options.closeOnClick] - 点击地图是否关闭窗口
   * @returns {Promise<void>}
   */
  async openInfoWindow (coordinate, content, options = {}) {
    if (!this.map) throw new Error('Map not initialized')
    await this.ensureSdkLoaded()

    // 关闭已存在的信息窗口
    this.closeInfoWindow()

    const [lng, lat] = coordinate
    const position = new this.AMap.LngLat(lng, lat)

    // 如果 infoWindow 不存在，创建它

      // 创建信息窗口实例
      this.infoWindow = new this.AMap.InfoWindow({

        anchor: 'bottom-center',
        ...options.infoWindowOptions,
        content: content,
        isCustom: true,
        offset: new this.AMap.Pixel(0, -20)
      })

    // 打开信息窗口
    this.infoWindow.open(this.map, position)

    // 如果设置了点击地图关闭，添加地图点击事件
    if (options.closeOnClick) {
      this._infoWindowCloseHandler = () => {
        this.closeInfoWindow()
      }
      this.map.on('click', this._infoWindowCloseHandler)
    }
  }

  /**
   * 关闭信息窗口
   * @returns {Promise<void>}
   */
  async closeInfoWindow () {
    if (this.infoWindow) {
      // 只关闭窗口，不删除实例（保留以便重用）
      this.infoWindow.close()
    }

    // 移除地图点击关闭事件
    if (this._infoWindowCloseHandler) {
      this.map.off('click', this._infoWindowCloseHandler)
      this._infoWindowCloseHandler = null
    }
  }

  // ===== 轨迹播放相关方法 =====

  /**
   * 添加轨迹
   * @param {Array<{key: string, path: Array<{x: number, y: number}>}>} segments - 轨迹段数组
   * @param {object} config - 轨迹配置
   */
  async addTrajectory (segments, config = {}) {
    if (!this.map) throw new Error('Map not initialized')
    if (!segments || segments.length === 0) {
      throw new Error('Trajectory segments cannot be empty')
    }

    // 确保 SDK 已加载
    await this.ensureSdkLoaded()

    // 清除旧的轨迹
    this.clearTrajectory()

    // 更新轨迹状态
    const state = this.trajectoryState
    state.segments = segments
    state.config = {
      speed: config.speed || 1,
      baseSpeed: config.baseSpeed || 10,
      unplayedColor: config.unplayedColor || '#ff0000',
      playedColor: config.playedColor || '#00ff00',
      startIcon: config.startIcon || require('@vue2-client/assets/img/map/start.png'),
      endIcon: config.endIcon || require('@vue2-client/assets/img/map/terminus.png'),
      carIcon: config.carIcon || require('@vue2-client/assets/img/map/car.png')
    }
    state.speed = config.speed || 1
    state.currentIndex = 0
    state.progress = 0
    state.status = 'idle'
    state._distanceTraveled = 0
    state._segmentLengths = []
    state._totalLength = 0
    state._coordinates = []
    state._lastTime = 0
    state._polylines = []
    state._traveledPolylines = []
    state._carMarker = null
    state._startMarkers = []
    state._endMarkers = []
    state._animationTimer = null

    // 创建轨迹图层
    this.initTrajectoryLayers()
  }

  /**
   * 初始化轨迹图层
   * @private
   */
  initTrajectoryLayers () {
    if (!this.map || !this.AMap) return

    const state = this.trajectoryState

    // 转换所有轨迹段坐标
    const allCoordinates = state.segments.map(segment => {
      return segment.path.map(point => [point.x, point.y])
    })

    // 创建轨迹线（未播放部分）
    state._polylines = allCoordinates.map((coords, index) => {
      if (coords.length < 2) return null
      return new this.AMap.Polyline({
        path: coords,
        strokeColor: state.config.unplayedColor,
        strokeWeight: 6,
        strokeOpacity: 1,
        showDir: true,
        zIndex: 50
      })
    }).filter(Boolean)

    state._polylines.forEach(polyline => {
      this.map.add(polyline)
    })

    // 创建起点和终点标记
    allCoordinates.forEach((coords, index) => {
      if (coords.length > 0) {
        // 起点
        const startMarker = new this.AMap.Marker({
          position: coords[0],
          icon: new this.AMap.Icon({
            image: state.config.startIcon,
            size: new this.AMap.Size(30, 30),
            imageSize: new this.AMap.Size(30, 30)
          }),
          anchor: 'bottom-center',
          zIndex: 100
        })
        this.map.add(startMarker)
        state._startMarkers.push(startMarker)

        // 终点
        const endMarker = new this.AMap.Marker({
          position: coords[coords.length - 1],
          icon: new this.AMap.Icon({
            image: state.config.endIcon,
            size: new this.AMap.Size(30, 30),
            imageSize: new this.AMap.Size(30, 30)
          }),
          anchor: 'bottom-center',
          zIndex: 100
        })
        this.map.add(endMarker)
        state._endMarkers.push(endMarker)
      }
    })

    // 创建小车标记
    const firstCoord = allCoordinates[0] && allCoordinates[0][0]
    if (firstCoord) {
      state._carMarker = new this.AMap.Marker({
        position: firstCoord,
        icon: new this.AMap.Icon({
          image: state.config.carIcon,
          size: new this.AMap.Size(24, 48),
          imageSize: new this.AMap.Size(24, 48)
        }),
        anchor: 'center',
        zIndex: 200
      })
      this.map.add(state._carMarker)
    }

    // 初始化第一段数据
    this.updateCurrentSegmentData()

    // 轨迹添加完成后，计算轨迹中心和合适的 zoom，然后调用 fitView
    this.fitTrajectoryView(allCoordinates)
  }

  /**
   * 根据轨迹坐标计算中心点和 zoom，并调用 fitView
   * @private
   * @param {Array} allCoordinates - 所有轨迹段的坐标数组
   */
  fitTrajectoryView (allCoordinates) {
    if (!allCoordinates || allCoordinates.length === 0) return

    // 展平所有坐标
    const allPoints = allCoordinates.flat()
    if (allPoints.length === 0) return

    // 计算边界框（bounding box）
    let minLng = allPoints[0][0]
    let maxLng = allPoints[0][0]
    let minLat = allPoints[0][1]
    let maxLat = allPoints[0][1]

    allPoints.forEach(point => {
      minLng = Math.min(minLng, point[0])
      maxLng = Math.max(maxLng, point[0])
      minLat = Math.min(minLat, point[1])
      maxLat = Math.max(maxLat, point[1])
    })

    // 计算中心点
    const centerLng = (minLng + maxLng) / 2
    const centerLat = (minLat + maxLat) / 2
    const center = [centerLng, centerLat]

    const zoom = this.calcSafeZoom([maxLng, minLat], [minLng, maxLat], this.map)

    // 调用 fitView 缩放地图
    this.fitView(zoom, center)
  }

  /**
   * 更新当前段数据
   * @private
   */
  updateCurrentSegmentData () {
    const state = this.trajectoryState
    if (state.currentIndex >= state.segments.length) {
      return
    }

    const segment = state.segments[state.currentIndex]
    state._coordinates = segment.path.map(point => [point.x, point.y])

    // 计算线段长度（使用经纬度距离计算）
    state._segmentLengths = []
    state._totalLength = 0
    if (state._coordinates.length >= 2) {
      for (let i = 0; i < state._coordinates.length - 1; i++) {
        const start = state._coordinates[i]
        const end = state._coordinates[i + 1]
        // 使用经纬度计算距离（米）
        const distance = this.calculateDistance(start, end)
        state._segmentLengths.push(distance)
        state._totalLength += distance
      }
    }
  }

  /**
   * 计算两点间距离（米）
   * @private
   */
  calculateDistance (point1, point2) {
    const R = 6371000 // 地球半径（米）
    const lat1 = point1[1] * Math.PI / 180
    const lat2 = point2[1] * Math.PI / 180
    const deltaLat = (point2[1] - point1[1]) * Math.PI / 180
    const deltaLng = (point2[0] - point1[0]) * Math.PI / 180

    const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
      Math.cos(lat1) * Math.cos(lat2) *
      Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2)
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))

    return R * c
  }

  /**
   * 根据距离比例获取位置
   * @private
   */
  getPositionByDistanceRatio (distanceRatio) {
    const state = this.trajectoryState
    if (state._coordinates.length < 2) {
      return { index: 0, position: state._coordinates[0] || [0, 0] }
    }

    const targetDistance = distanceRatio * state._totalLength
    let accumulatedDistance = 0

    for (let i = 0; i < state._segmentLengths.length; i++) {
      const segmentLength = state._segmentLengths[i]

      if (accumulatedDistance + segmentLength >= targetDistance) {
        const segmentRatio = (targetDistance - accumulatedDistance) / segmentLength
        const start = state._coordinates[i]
        const end = state._coordinates[i + 1]

        // 线性插值
        const position = [
          start[0] + segmentRatio * (end[0] - start[0]),
          start[1] + segmentRatio * (end[1] - start[1])
        ]

        return { index: i, position }
      }

      accumulatedDistance += segmentLength
    }

    return {
      index: state._coordinates.length - 2,
      position: state._coordinates[state._coordinates.length - 1]
    }
  }

  /**
   * 轨迹动画帧处理
   * @private
   */
  trajectoryAnimationFrame () {
    const state = this.trajectoryState
    if (state.status !== 'playing') return

    const now = Date.now()
    const deltaSeconds = Math.max((now - state._lastTime) / 1000, 0)
    state._lastTime = now

    // 计算移动距离（米转地图单位，高德地图使用米作为单位）
    const actualSpeed = state.config.baseSpeed * state.speed
    const incrementDistance = actualSpeed * deltaSeconds
    state._distanceTraveled += incrementDistance

    // 更新进度
    state.progress = state._totalLength > 0
      ? state._distanceTraveled / state._totalLength
      : 1

    // 检查是否完成当前段
    if (state.progress >= 1) {
      state.progress = 1

      // 添加完整已播放轨迹（标记为永久轨迹，不会被清除）
      const traveledPolyline = new this.AMap.Polyline({
        path: state._coordinates,
        strokeColor: state.config.playedColor,
        strokeWeight: 6,
        strokeOpacity: 1,
        showDir: true,
        zIndex: 60
      })
      traveledPolyline._permanent = true // 标记为永久轨迹
      traveledPolyline._segmentIndex = state.currentIndex // 记录段索引
      this.map.add(traveledPolyline)
      state._traveledPolylines.push(traveledPolyline)

      // 隐藏当前段的未播放轨迹
      // if (state._polylines[state.currentIndex]) {
      //   state._polylines[state.currentIndex].hide()
      // }

      // 检查是否有下一段
      if (state.currentIndex < state.segments.length - 1) {
        // 移动到下一段
        state.currentIndex++
        state.progress = 0
        state._distanceTraveled = 0
        this.updateCurrentSegmentData()

        // 移动小车到下一段起点
        if (state._carMarker && state._coordinates[0]) {
          state._carMarker.setPosition(state._coordinates[0])
        }
      } else {
        // 所有段播放完成
        state.status = 'finished'
        if (state._animationTimer) {
          clearTimeout(state._animationTimer)
          state._animationTimer = null
        }
        // 移动小车到终点
        if (state._carMarker && state._coordinates.length > 0) {
          const lastCoord = state._coordinates[state._coordinates.length - 1]
          state._carMarker.setPosition(lastCoord)
        }
        return
      }
    }

    // 更新小车位置和已播放轨迹
    const { index, position } = this.getPositionByDistanceRatio(state.progress)

    // 更新小车位置
    if (state._carMarker) {
      state._carMarker.setPosition(position)

      // 更新小车旋转角度
      // 高德地图角度系统：0度=正东，90度=正北，180度=正西，270度=正南（逆时针）
      // 如果小车图标默认朝上（正北），需要计算从当前位置到下一个位置的方向
      const nextIndex = Math.min(index + 1, state._coordinates.length - 1)
      const nextCoord = state._coordinates[nextIndex]
      const currentCoord = state._coordinates[index]
      const dx = nextCoord[0] - currentCoord[0]
      const dy = nextCoord[1] - currentCoord[1]

      // 计算角度：Math.atan2(dy, dx) 返回从正东方向逆时针到向量的角度（弧度）
      // 高德地图角度系统：0度=正东，90度=正北，180度=正西，270度=正南（逆时针）
      // 解决方案：使用 Math.atan2(dx, dy) 交换坐标顺序，然后加上90度偏移
      // 这样可以同时正确处理东西方向和南北方向
      const angleRad = Math.atan2(dx, dy)
      let angleDeg = angleRad * 180 / Math.PI

      // 规范化角度到0-360度范围
      if (angleDeg < 0) {
        angleDeg += 360
      } else if (angleDeg >= 360) {
        angleDeg -= 360
      }

      state._carMarker.setAngle(angleDeg)
    }

    // 更新已播放轨迹（临时）
    // 清除当前段的临时轨迹（使用索引判断），保留永久轨迹
    const currentTempPolylines = []
    const permanentPolylines = []
    state._traveledPolylines.forEach((p) => {
      // 只清除当前段的临时轨迹，保留所有永久轨迹和其他段的轨迹
      if (p._tempIndex === state.currentIndex && !p._permanent) {
        currentTempPolylines.push(p)
      } else {
        permanentPolylines.push(p)
      }
    })
    currentTempPolylines.forEach(p => {
      this.map.remove(p)
    })
    state._traveledPolylines = permanentPolylines

    // 添加新的临时轨迹
    const splitIndex = index + 1
    const traveledCoords = state._coordinates.slice(0, splitIndex)
    traveledCoords.push(position)

    if (traveledCoords.length >= 2) {
      const traveledPolyline = new this.AMap.Polyline({
        path: traveledCoords,
        strokeColor: state.config.playedColor,
        strokeWeight: 6,
        strokeOpacity: 1,
        showDir: true,
        zIndex: 60
      })
      traveledPolyline._tempIndex = state.currentIndex
      traveledPolyline._permanent = false
      this.map.add(traveledPolyline)
      state._traveledPolylines.push(traveledPolyline)
    }

    // 继续下一帧
    state._animationTimer = setTimeout(() => {
      this.trajectoryAnimationFrame()
    }, 16) // 约60fps
  }

  /**
   * 开始播放轨迹
   */
  async startTrajectory () {
    const state = this.trajectoryState
    if (!this.map || !state.segments || state.segments.length === 0) {
      throw new Error('Trajectory not added')
    }

    if (state.status === 'finished' || (state.progress === 0 && state.currentIndex === 0)) {
      return this.replayTrajectory()
    } else {
      return this.resumeTrajectory()
    }
  }

  /**
   * 重新播放轨迹
   */
  async replayTrajectory () {
    const state = this.trajectoryState
    if (!this.map || !state.segments || state.segments.length === 0) {
      throw new Error('Trajectory not added')
    }

    // 暂停当前播放
    this.pauseTrajectory()

    // 重置状态
    state.currentIndex = 0
    state.progress = 0
    state._distanceTraveled = 0
    state.status = 'idle'

    // 清除已播放轨迹
    state._traveledPolylines.forEach(polyline => {
      this.map.remove(polyline)
    })
    state._traveledPolylines = []

    // 显示所有未播放轨迹
    state._polylines.forEach(polyline => {
      if (polyline) {
        polyline.show()
      }
    })

    // 更新当前段数据
    this.updateCurrentSegmentData()

    // 移动小车到起点
    if (state._carMarker && state._coordinates && state._coordinates[0]) {
      state._carMarker.setPosition(state._coordinates[0])
    }

    // 开始播放
    this.resumeTrajectory()
  }

  /**
   * 暂停播放轨迹
   */
  async pauseTrajectory () {
    const state = this.trajectoryState
    if (state.status !== 'playing') return

    state.status = 'paused'

    // 清除动画定时器
    if (state._animationTimer) {
      clearTimeout(state._animationTimer)
      state._animationTimer = null
    }

    // 将临时轨迹标记为永久
    state._traveledPolylines.forEach(p => {
      if (p) {
        p._permanent = true
      }
    })
  }

  /**
   * 继续播放轨迹
   */
  async resumeTrajectory () {
    const state = this.trajectoryState
    if (!this.map || !state.segments || state.segments.length === 0) {
      throw new Error('Trajectory not added')
    }

    if (state.status === 'finished') {
      return this.replayTrajectory()
    }

    state.status = 'playing'
    state._lastTime = Date.now()

    // 确保当前段数据已更新
    this.updateCurrentSegmentData()

    // 开始动画
    this.trajectoryAnimationFrame()
  }

  /**
   * 清除轨迹
   */
  async clearTrajectory () {
    const state = this.trajectoryState
    // 暂停播放
    this.pauseTrajectory()

    // 移除所有轨迹元素
    if (this.map) {
      if (state._polylines) {
        state._polylines.forEach(polyline => {
          if (polyline) {
            this.map.remove(polyline)
          }
        })
      }
      if (state._traveledPolylines) {
        state._traveledPolylines.forEach(polyline => {
          if (polyline) {
            this.map.remove(polyline)
          }
        })
      }
      if (state._carMarker) {
        this.map.remove(state._carMarker)
      }
      if (state._startMarkers) {
        state._startMarkers.forEach(marker => {
          if (marker) {
            this.map.remove(marker)
          }
        })
      }
      if (state._endMarkers) {
        state._endMarkers.forEach(marker => {
          if (marker) {
            this.map.remove(marker)
          }
        })
      }
    }

    // 重置状态
    state.segments = []
    state.config = null
    state.currentIndex = 0
    state.progress = 0
    state.status = 'idle'
    state._distanceTraveled = 0
    state._segmentLengths = []
    state._totalLength = 0
    state._coordinates = []
    state._polylines = []
    state._traveledPolylines = []
    state._carMarker = null
    state._startMarkers = []
    state._endMarkers = []
    state._animationTimer = null
  }

  /**
   * 获取轨迹播放状态（返回可序列化的状态，不包含内部引用）
   */
  /**
   * 设置轨迹播放状态
   * @param {object|string} stateOrKey - 完整状态对象或字段名
   * @param {*} value - 字段值（当第一个参数为字段名时）
   */
  setTrajectoryState (stateOrKey, value) {
    if (typeof stateOrKey === 'string') {
      // 设置单个字段（允许设置内部字段，因为这是内部方法）
      this.trajectoryState[stateOrKey] = value
      // 如果更新了 speed，同步到 config
      if (stateOrKey === 'speed' && this.trajectoryState.config) {
        this.trajectoryState.config.speed = value
      }
    } else {
      // 设置整个状态对象（只更新提供的字段，不覆盖整个对象）
      Object.keys(stateOrKey).forEach(key => {
        this.trajectoryState[key] = stateOrKey[key]
      })
      // 同步 speed 到 config
      if (stateOrKey.speed && this.trajectoryState.config) {
        this.trajectoryState.config.speed = stateOrKey.speed
      }
    }
  }

  /**
   * 获取轨迹播放状态
   * @param {string} [key] - 可选，获取指定字段
   * @returns {object|*} 完整状态对象或指定字段值
   */
  getTrajectoryState (key) {
    if (key) {
      return this.trajectoryState[key]
    }
    // 返回可序列化的状态（排除内部字段和地图元素引用）
    const state = this.trajectoryState
    return {
      segments: state.segments || [],
      currentIndex: state.currentIndex || 0,
      progress: state.progress || 0,
      speed: state.speed || 1,
      config: state.config || {},
      status: state.status || 'idle'
    }
  }

  coord4326To3857 (x, y) {
    const result = this.coords4326To3857([[x, y]])
    return result.length ? result[0] : null
  }

  coords4326To3857 (coords) {
    if (!Array.isArray(coords)) {
      console.warn('coords4326To3857 参数必须是数组', coords)
      return []
    }

    return coords
      .map(item => {
        if (!Array.isArray(item) || item.length !== 2) {
          console.warn('非法坐标:', item)
          return null
        }

        return transform(item, 'EPSG:4326', 'EPSG:3857')
      })
      .filter(Boolean)
  }
}
