<script setup>
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { getTownMapMarkers } from '@/api/home'
import { useStatYear } from '@/stores/statYear'
import {
  getGisAccessToken,
  getGisDefaultCenter,
  getGisDefaultZoom,
  getGisMaxZoom,
  getGisMinZoom,
  isGisConfigured,
  loadGisSdk,
  resolveGisMapStyle,
  createGisTransformRequest,
  syncWmtsLayerVisibility 
} from '@/utils/gisLoader'
import {
  parsePoiLngLat,
  searchNearbyPois
} from '@/utils/gisPoiSearch'
import {
  addTownshipBoundaryLayers,
  bindTownshipBoundaryClick,
  buildTownshipInfo,
  flyToTownship,
  getTownshipLabelPoints,
  removeTownshipBoundaryLayers
} from '@/utils/gisTownshipBoundary'
import { getTownshipLivestockStats, markersToTownshipLabels } from '@/constants/townshipLivestock'

const props = defineProps({
  center: {
    type: Array,
    default: null
  },
  zoom: {
    type: Number,
    default: null
  },
  interactive: {
    type: Boolean,
    default: true
  },
  showNav: {
    type: Boolean,
    default: false
  },
  showScale: {
    type: Boolean,
    default: false
  },
  /** 是否展示地图中心点周边 POI（移动地图后防抖查询） */
  showPoiMarkers: {
    type: Boolean,
    default: true
  },
  /** 是否展示巴青县乡镇边界（map.json）及名称标注 */
  showTownshipBoundary: {
    type: Boolean,
    default: true
  },
  /** 乡镇存栏/出栏覆盖，如 { 岗切乡: { inventory: 1, outbound: 2 } } */
  townshipLabels: {
    type: Object,
    default: () => ({})
  },
  /** 点击乡镇是否下钻到该乡镇范围 */
  townshipDrillDown: {
    type: Boolean,
    default: true
  },
  /** 点击乡镇下钻 zoom 级别 */
  townshipDrillMaxZoom: {
    type: Number,
    default: 16
  }
})

const emit = defineEmits(['ready', 'click', 'township-click'])

const { statYear } = useStatYear()
const containerRef = ref(null)
const townshipLabelData = ref({})

async function loadTownMarkers() {
  try {
    const res = await getTownMapMarkers(statYear.value)
    townshipLabelData.value = markersToTownshipLabels(res.data?.markers)
  } catch (e) {
    townshipLabelData.value = {}
    console.warn('[GisMap] townMapMarkers load failed', e)
  }
}

let map = null
let cmmapglRef = null
let clampingZoom = false
let unbindWheelZoom = null

/** 滚轮缩放防抖：停止滚动后再合并应用，减少瓦片 canceled */
const WHEEL_ZOOM_DEBOUNCE_MS = 150
const WHEEL_ZOOM_RATE = 0.0022
/** 地图移动结束后防抖再查 POI，避免拖拽过程中频繁请求 */
const POI_SEARCH_DEBOUNCE_MS = 400
/** 仅 zoom >= 16 时查询并展示 POI */
const POI_SEARCH_MIN_ZOOM = 16

let wheelDeltaAccum = 0
let wheelDebounceTimer = null
let lastWheelAround = null
let poiMarkers = []
let townshipMarkers = []
let unbindTownshipClick = null
let unbindPoiSearch = null
let poiSearchDebounceTimer = null

function getMapViewCenter() {
  if (!map || typeof map.getCenter !== 'function') {
    return null
  }
  const center = map.getCenter()
  const lng = Number(center?.lng ?? center?.[0])
  const lat = Number(center?.lat ?? center?.[1])
  if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
    return null
  }
  return [lng, lat]
}

function getMapZoom() {
  if (!map || typeof map.getZoom !== 'function') {
    return null
  }
  const zoom = map.getZoom()
  return Number.isFinite(zoom) ? zoom : null
}

function shouldSearchPoiAtCurrentZoom() {
  const zoom = getMapZoom()
  return zoom != null && zoom >= POI_SEARCH_MIN_ZOOM
}

function schedulePoiSearch() {
  if (!props.showPoiMarkers || !map) {
    return
  }
  if (poiSearchDebounceTimer) {
    clearTimeout(poiSearchDebounceTimer)
  }
  poiSearchDebounceTimer = setTimeout(() => {
    poiSearchDebounceTimer = null
    if (!shouldSearchPoiAtCurrentZoom()) {
      clearPoiMarkers()
      return
    }
    renderPoiMarkers()
  }, POI_SEARCH_DEBOUNCE_MS)
}

function bindPoiSearchListener() {
  if (!map || typeof map.on !== 'function') {
    return () => {}
  }
  const onMoveEnd = () => {
    schedulePoiSearch()
  }
  map.on('moveend', onMoveEnd)
  return () => {
    if (poiSearchDebounceTimer) {
      clearTimeout(poiSearchDebounceTimer)
      poiSearchDebounceTimer = null
    }
    map.off('moveend', onMoveEnd)
  }
}

function logClickCoordinate(lngLat) {
  if (!lngLat) {
    return
  }
  const lng = Number(lngLat.lng ?? lngLat[0])
  const lat = Number(lngLat.lat ?? lngLat[1])
  if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
    return
  }
  console.log(
    `[GIS] click | lng: ${lng.toFixed(6)}, lat: ${lat.toFixed(6)} | [${lng.toFixed(6)}, ${lat.toFixed(6)}]`
  )
}

function bindMapListeners() {
  if (!map) {
    return
  }
  map.on('zoomend', () => {
    snapZoomToLimits()
    syncWmtsLayerVisibility(map)
  })
  map.on('zoom', () => {
    syncWmtsLayerVisibility(map)
  })
  map.on('click', (e) => {
    logClickCoordinate(e.lngLat)
    emit('click', e)
  })
}

function clearPoiMarkers() {
  poiMarkers.forEach((marker) => {
    try {
      marker.remove()
    } catch {
      /* ignore */
    }
  })
  poiMarkers = []
}

function clearTownshipMarkers() {
  townshipMarkers.forEach((marker) => {
    try {
      marker.remove()
    } catch {
      /* ignore */
    }
  })
  townshipMarkers = []
}

function teardownTownshipBoundary() {
  if (typeof unbindTownshipClick === 'function') {
    unbindTownshipClick()
    unbindTownshipClick = null
  }
  clearTownshipMarkers()
  if (map) {
    removeTownshipBoundaryLayers(map)
  }
}

function bindTownshipDrillDown() {
  if (!props.townshipDrillDown || !map) {
    return
  }

  unbindTownshipClick = bindTownshipBoundaryClick(map, (feature) => {
    const info = buildTownshipInfo(feature)
    if (!info) {
      return
    }

    flyToTownship(map, feature, {
      zoom: props.townshipDrillMaxZoom
    })
    emit('township-click', info)
  })
}

function createTownshipMarkerElement(name, stats) {
  const wrap = document.createElement('div')
  wrap.className = 'gis-township-marker'

  const nameEl = document.createElement('div')
  nameEl.className = 'gis-township-marker__name'
  nameEl.textContent = String(name || '')
  wrap.appendChild(nameEl)

  if (stats && stats.inventory != null) {
    const inventoryEl = document.createElement('div')
    inventoryEl.className = 'gis-township-marker__stat'
    inventoryEl.textContent = `入栏：${stats.inventory}`
    wrap.appendChild(inventoryEl)
  }

  if (stats && stats.outbound != null) {
    const outboundEl = document.createElement('div')
    outboundEl.className = 'gis-township-marker__stat gis-township-marker__stat--out'
    outboundEl.textContent = `出栏：${stats.outbound}`
    wrap.appendChild(outboundEl)
  }

  return wrap
}

function renderTownshipBoundary() {
  if (!props.showTownshipBoundary || !map || !cmmapglRef) {
    return
  }

  teardownTownshipBoundary()
  if (!addTownshipBoundaryLayers(map)) {
    return
  }
  bindTownshipDrillDown()

  const Marker = cmmapglRef.Marker
  if (typeof Marker !== 'function') {
    return
  }

  getTownshipLabelPoints().forEach((item) => {
    const stats = getTownshipLivestockStats(item.name, {
      ...townshipLabelData.value,
      ...props.townshipLabels
    })
    const marker = new Marker({
      element: createTownshipMarkerElement(item.name, stats),
      anchor: 'center'
    })
      .setLngLat(item.lngLat)
      .addTo(map)
    townshipMarkers.push(marker)
  })
}

function escapeHtml(text) {
  return String(text || '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
}

function buildPoiPopupHtml(poi) {
  const name = escapeHtml(poi.name || '未知地点')
  const address = poi.address ? escapeHtml(poi.address) : ''
  const distance = poi.distance ? `${escapeHtml(poi.distance)}m` : ''
  const parts = [`<div style="font-weight:600;margin-bottom:4px">${name}</div>`]
  if (address) {
    parts.push(`<div style="font-size:12px;opacity:0.85">${address}</div>`)
  }
  if (distance) {
    parts.push(`<div style="font-size:12px;opacity:0.7;margin-top:4px">距中心 ${distance}</div>`)
  }
  return parts.join('')
}

/** 自定义 Marker：图标下方常驻显示 POI 名称 */
function createPoiMarkerElement(name) {
  const wrap = document.createElement('div')
  wrap.className = 'gis-poi-marker'
  wrap.style.pointerEvents = 'auto'

  const label = document.createElement('div')
  label.className = 'gis-poi-marker__label'
  label.textContent = String(name || '未知地点')
  label.title = label.textContent

  const dot = document.createElement('div')
  dot.className = 'gis-poi-marker__dot'

  wrap.appendChild(label)
  wrap.appendChild(dot)
  return wrap
}

async function renderPoiMarkers() {
  if (!props.showPoiMarkers || !map || !cmmapglRef) {
    return
  }
  if (!shouldSearchPoiAtCurrentZoom()) {
    clearPoiMarkers()
    return
  }

  const center = getMapViewCenter()
  if (!center) {
    return
  }

  clearPoiMarkers()

  let pois = []
  try {
    pois = await searchNearbyPois({ center })
  } catch {
    return
  }

  const Marker = cmmapglRef.Marker
  const Popup = cmmapglRef.Popup
  if (typeof Marker !== 'function') {
    return
  }

  pois.forEach((poi) => {
    const lngLat = parsePoiLngLat(poi.location)
    if (!lngLat) {
      return
    }
    const markerOpts = {
      element: createPoiMarkerElement(poi.name),
      anchor: 'bottom'
    }
    const marker = new Marker(markerOpts).setLngLat(lngLat)
    if (typeof Popup === 'function') {
      marker.setPopup(
        new Popup({ offset: 20, closeOnClick: true, maxWidth: '260px' }).setHTML(
          buildPoiPopupHtml(poi)
        )
      )
    }
    marker.addTo(map)
    poiMarkers.push(marker)
  })
}

function destroyMap() {
  if (typeof unbindPoiSearch === 'function') {
    unbindPoiSearch()
    unbindPoiSearch = null
  }
  if (typeof unbindWheelZoom === 'function') {
    unbindWheelZoom()
    unbindWheelZoom = null
  }
  wheelDeltaAccum = 0
  lastWheelAround = null
  clampingZoom = false
  clearPoiMarkers()
  teardownTownshipBoundary()
  if (map) {
    map.remove()
    map = null
  }
}

function resolveCenter() {
  if (Array.isArray(props.center) && props.center.length >= 2) {
    return [Number(props.center[0]), Number(props.center[1])]
  }
  return getGisDefaultCenter()
}

function resolveZoom() {
  if (props.zoom != null && Number.isFinite(Number(props.zoom))) {
    return Number(props.zoom)
  }
  return getGisDefaultZoom()
}

function applyMapLimits() {
  if (!map) {
    return
  }
  const minZ = getGisMinZoom()
  const maxZ = getGisMaxZoom()
  if (typeof map.setMinZoom === 'function') {
    map.setMinZoom(minZ)
  }
  if (typeof map.setMaxZoom === 'function') {
    map.setMaxZoom(maxZ)
  }
}

/** 任何越界立即拉回（不用 0.01 容差，否则 17.005 时图层会被 style maxzoom 隐藏） */
function snapZoomToLimits() {
  if (!map || clampingZoom) {
    return
  }
  const minZ = getGisMinZoom()
  const maxZ = getGisMaxZoom()
  const z = map.getZoom()
  if (!Number.isFinite(z)) {
    return
  }
  if (z <= maxZ && z >= minZ) {
    return
  }
  const next = Math.min(maxZ, Math.max(minZ, z))
  clampingZoom = true
  try {
    map.jumpTo({ zoom: next })
  } finally {
    clampingZoom = false
  }
}

function bindZoomClampListener() {
  if (!map) {
    return
  }
  map.on('zoom', () => {
    if (clampingZoom || !map) {
      return
    }
    const maxZ = getGisMaxZoom()
    const minZ = getGisMinZoom()
    const z = map.getZoom()
    if (!Number.isFinite(z) || (z <= maxZ && z >= minZ)) {
      return
    }
    snapZoomToLimits()
  })
}

/** 滚轮防抖缩放：拦截原生 scrollZoom，停止滚动后一次性应用累计 delta */
function bindDebouncedWheelZoom() {
  const el = containerRef.value
  if (!el || !map) {
    return () => {}
  }

  const flushWheelZoom = () => {
    wheelDebounceTimer = null
    if (!map || wheelDeltaAccum === 0) {
      return
    }
    const minZ = getGisMinZoom()
    const maxZ = getGisMaxZoom()
    const z = map.getZoom()
    if (!Number.isFinite(z)) {
      wheelDeltaAccum = 0
      return
    }

    const delta = wheelDeltaAccum
    wheelDeltaAccum = 0

    let targetZ = z - delta * WHEEL_ZOOM_RATE
    targetZ = Math.min(maxZ, Math.max(minZ, targetZ))
    if (Math.abs(targetZ - z) < 0.001) {
      return
    }

    const around = lastWheelAround || (typeof map.getCenter === 'function' ? map.getCenter() : null)
    lastWheelAround = null

    const nearLimit = targetZ >= maxZ - 0.05 || targetZ <= minZ + 0.05
    if (nearLimit && typeof map.jumpTo === 'function') {
      map.jumpTo(around ? { zoom: targetZ, around } : { zoom: targetZ })
      return
    }

    if (around && typeof map.easeTo === 'function') {
      map.easeTo({
        zoom: targetZ,
        around,
        duration: 180,
        essential: true
      })
    } else if (typeof map.setZoom === 'function') {
      map.setZoom(targetZ)
    }
  }

  const onWheel = (e) => {
    if (!map || !props.interactive) {
      return
    }

    e.preventDefault()
    e.stopImmediatePropagation()

    const minZ = getGisMinZoom()
    const maxZ = getGisMaxZoom()
    const z = map.getZoom()
    if (!Number.isFinite(z)) {
      return
    }
    if (e.deltaY < 0 && z >= maxZ - 0.01) {
      return
    }
    if (e.deltaY > 0 && z <= minZ + 0.01) {
      return
    }

    const rect = el.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top
    if (typeof map.unproject === 'function') {
      lastWheelAround = map.unproject([x, y])
    }

    wheelDeltaAccum += e.deltaY
    if (wheelDebounceTimer) {
      clearTimeout(wheelDebounceTimer)
    }
    wheelDebounceTimer = setTimeout(flushWheelZoom, WHEEL_ZOOM_DEBOUNCE_MS)
  }

  el.addEventListener('wheel', onWheel, { passive: false, capture: true })
  return () => {
    if (wheelDebounceTimer) {
      clearTimeout(wheelDebounceTimer)
      wheelDebounceTimer = null
    }
    wheelDeltaAccum = 0
    lastWheelAround = null
    el.removeEventListener('wheel', onWheel, { capture: true })
  }
}

function configureScrollZoom() {
  if (!map?.scrollZoom) {
    return
  }
  if (typeof map.scrollZoom.disable === 'function') {
    map.scrollZoom.disable()
  }
}

function resizeMap() {
  if (!map) {
    return
  }
  if (typeof map.setPixelRatio === 'function') {
    map.setPixelRatio(1)
  }
  if (typeof map.resize === 'function') {
    map.resize()
  }
}

async function initMap() {
  destroyMap()
  if (!isGisConfigured()) {
    return
  }
  try {
    const cmmapgl = await loadGisSdk()
    if (typeof cmmapgl.setMaxParallelImageRequests === 'function') {
      cmmapgl.setMaxParallelImageRequests(64)
    }
    cmmapglRef = cmmapgl
    const token = getGisAccessToken()
    if (token) {
      cmmapgl.accessToken = token
    }
    const style = resolveGisMapStyle()
    map = new cmmapgl.Map({
      container: containerRef.value,
      center: resolveCenter(),
      zoom: resolveZoom(),
      minZoom: getGisMinZoom(),
      maxZoom: getGisMaxZoom(),
      style,
      interactive: props.interactive,
      accessToken: token || undefined,
      collectResourceTiming: false,
      pixelRatio: 1,
      transformRequest: createGisTransformRequest()
    })
    if (props.showNav) {
      map.addControl(new cmmapgl.NavigationControl(), 'top-right')
    }
    if (props.showScale) {
      map.addControl(new cmmapgl.ScaleControl({ unit: 'metric' }), 'bottom-left')
    }
    bindMapListeners()
    bindZoomClampListener()
    unbindWheelZoom = bindDebouncedWheelZoom()
    unbindPoiSearch = bindPoiSearchListener()
    configureScrollZoom()
    map.on('load', () => {
      applyMapLimits()
      configureScrollZoom()
      snapZoomToLimits()
      syncWmtsLayerVisibility(map)
      resizeMap()
      renderTownshipBoundary()
      schedulePoiSearch()
      emit('ready', map)
    })
    // 瓦片/遥测等非致命错误不向页面报错
    map.on('error', () => {})
  } catch {
    /* 静默失败，不遮挡大屏 */
  }
}

watch(
  () => [props.center, props.zoom],
  () => {
    if (!map || !cmmapglRef) return
    map.jumpTo({
      center: resolveCenter(),
      zoom: resolveZoom()
    })
  },
  { deep: true }
)

watch(
  () => props.showPoiMarkers,
  (show) => {
    if (!map || !cmmapglRef) return
    if (show) {
      schedulePoiSearch()
    } else {
      if (poiSearchDebounceTimer) {
        clearTimeout(poiSearchDebounceTimer)
        poiSearchDebounceTimer = null
      }
      clearPoiMarkers()
    }
  }
)

watch(
  () => [props.showTownshipBoundary, props.townshipLabels, props.townshipDrillDown, townshipLabelData.value],
  () => {
    if (!map || !cmmapglRef) return
    if (props.showTownshipBoundary) {
      renderTownshipBoundary()
    } else {
      teardownTownshipBoundary()
    }
  },
  { deep: true }
)

watch(statYear, () => {
  loadTownMarkers()
})

onMounted(async () => {
  await loadTownMarkers()
  initMap()
})

onUnmounted(() => {
  destroyMap()
})

defineExpose({ resizeMap, getMap: () => map })
</script>

<template>
  <div class="gis-map">
    <div ref="containerRef" class="gis-map__canvas" />
  </div>
</template>

<style scoped>
.gis-map {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 120px;
  overflow: hidden;
}

.gis-map__canvas {
  width: 100%;
  height: 100%;
}
</style>

<!-- Marker DOM 由地图 SDK 挂载，需非 scoped 样式 -->
<style>
.gis-poi-marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  transform: translateY(-2px);
}

.gis-poi-marker__label {
  max-width: 108px;
  margin-bottom: 3px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(8, 28, 24, 0.82);
  border: 1px solid rgba(240, 180, 41, 0.45);
  color: #eef8f4;
  font-size: 11px;
  line-height: 1.35;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

.gis-poi-marker__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #f0b429;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  flex-shrink: 0;
}

.gis-township-marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  pointer-events: none;
  text-align: center;
  padding: 5px;
  border-radius: 6px;
  background: rgba(8, 24, 22, 0.88);
  border: 1px solid rgba(0, 220, 190, 0.35);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  transform: translateY(-4px);
}

.gis-township-marker__name {
  color: #e8fff9;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.gis-township-marker__stat {
  color: #7dffe8;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
}

.gis-township-marker__stat--out {
  color: #f0b429;
}
</style>
