<template>
  <div class="forest-grass-wet">
    <map-scene
      ref="mapSceneRef"
      :active-layer="activeLayer"
      :map-ranking="mapRanking"
      :admin-boundary-line-scale="2.4"
      :initial-zoom="9.55"
      hide-town-label-stats
      :enable-imagery-feature-pick="false"
      :enable-town-polygon-pick="false"
      @feature-loading="featureLoading = $event"
      @feature-info="handleFeatureInfo"
      @map-click="handleMapClick"
      @layer-status="handleLayerStatus"
    />

    <div class="forest-grass-wet-ui" id="forest-grass-wet-ui">
      <div class="fgw-left">
        <div class="fgw-side-stack is-left">
          <m-card
            v-for="panel in leftPanels"
            :key="getPanelRenderKey(panel, 'left')"
            class="fgw-side-card left-card"
            :class="{ 'is-double': isDoublePanel(panel) }"
            :title="panel.title"
            :width="sideCardWidth"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer :panel="panel" compact column-label-standard />
          </m-card>
        </div>
      </div>

      <div class="fgw-right">
        <div class="fgw-side-stack is-right">
          <m-card
            v-for="panel in rightPanels"
            :key="getPanelRenderKey(panel, 'right')"
            class="fgw-side-card right-card"
            :class="{ 'is-double': isDoublePanel(panel) }"
            :title="panel.title"
            :width="sideCardWidth"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer :panel="panel" compact column-label-standard />
          </m-card>
        </div>
      </div>

      <div
        v-if="layerPanelVisible"
        class="fgw-map-layer-tools"
        :style="{ height: `${layerPanelHeight}px` }"
      >
        <layer-panel
          v-model="layerPanelValue"
          v-model:selected-legend-key="selectedLegendKey"
          :topic-key="activeTopicKey"
          :layers="layerPanelLayers"
          :legends="activeLayerLegends"
          :multiple="activeTopicKey === 'overview'"
          :width="248"
          :height="layerPanelHeight"
          :compact="layerPanelCompact"
        />
      </div>

      <div v-if="ndviTimelineVisible" class="fgw-ndvi-timeline">
        <grassland-ndvi-timeline
          v-model="selectedNdviTimelineKey"
          :items="ndviTimelineRows"
          :loading="ndviTimelineStatus === 'loading'"
        />
      </div>

      <div v-if="featurePanelVisible" class="fgw-map-feature-info" :style="featurePanelStyle">
        <feature-info-panel
          :feature="selectedFeature"
          :empty="featureEmpty"
          :loading="featureLoading"
          :width="460"
          :height="featurePanelHeight"
        />
      </div>

      <div class="fgw-bottom-topic">
        <topic-switcher v-model="activeTopicKey" />
      </div>
    </div>

    <div v-if="loadingVisible" class="loading">
      <div class="loading-text">
        <span style="--index: 1">L</span>
        <span style="--index: 2">O</span>
        <span style="--index: 3">A</span>
        <span style="--index: 4">D</span>
        <span style="--index: 5">I</span>
        <span style="--index: 6">N</span>
        <span style="--index: 7">G</span>
      </div>
      <div class="loading-progress">
        <span class="value">{{ progress }}</span>
        <span class="unit">%</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
import gsap from "gsap"
import { Assets } from "./assets"
import {
  forestGrassWetTopics,
  getLayerByKey,
  getLayersByTopic,
  getTopicConfig,
} from "@/config/layers"
import { getBasicGrasslandClickInfoByWkt, getLayerTownRanking, getTopicBusinessStats, getWoodlandClickInfoByWkt } from "@/services/openApi"
import { getLandClickInfo, getWetlandClickInfo } from "@/services/pastureApi"
import { getGrasslandNdviTimeline } from "@/services/ndviTimeline"
import { SCREEN2_LAYOUT, getScreen2SideCardHeight, getScreen2SpanHeight } from "@/utils/screen2Standard"
import MapScene from "@/components/CesiumMap/index.vue"
import TopicSwitcher from "./components/TopicSwitcher.vue"
import LayerPanel from "./components/LayerPanel.vue"
import FeatureInfoPanel from "./components/FeatureInfoPanel.vue"
import BusinessPanelRenderer from "./components/BusinessPanelRenderer.vue"
import GrasslandNdviTimeline from "./components/GrasslandNdviTimeline.vue"
import mCard from "@/components/mCard/index.vue"

const props = defineProps({
  showLoading: {
    type: Boolean,
    default: true,
  },
  active: {
    type: Boolean,
    default: true,
  },
})

const emit = defineEmits(["topic-stats"])
const mapSceneRef = ref(null)
const activeTopicKey = ref(forestGrassWetTopics[0].key)
const activeLayerKey = ref("")
const overviewActiveLayerKeys = ref([])
const coreStats = ref([])
const coreStatus = ref("idle")
const coreLoading = ref(false)
const businessLeft = ref({ title: "业务统计", rows: [] })
const businessRight = ref({ title: "业务统计", rows: [] })
const leftPanels = ref([])
const rightPanels = ref([])
const mapRanking = ref(null)
const selectedLegendKey = ref("")
const selectedFeature = ref(null)
const featureEmpty = ref(false)
const featureLoading = ref(false)
const featurePanelPoint = ref(null)
const progress = ref(0)
const loadingVisible = ref(props.showLoading)
const ndviTimelineRows = ref([])
const ndviTimelineStatus = ref("idle")
const selectedNdviTimelineKey = ref("")
const layerStatus = ref({
  type: "idle",
  text: "等待 GeoServer 图层加载",
})

let assets = null
let destroyed = false
let mapAssetsReady = false
let mapLoadStarted = false
let mapLoadTimer = null
let rankingRequestId = 0
let businessRequestId = 0
let ndviTimelineRequestId = 0
let featureQueryRequestId = 0
let resettingTopicLayer = false
let resettingLegend = false

const magicDetailTitleFieldMap = {
  图斑编号: "patch_no",
  图斑号: "patch_no",
  湿地编号: "land_number",
  草地编号: "land_number",
  林地类型: "dlmc",
  地类: "landUseName",
  地类名称: "landUseName",
  所在乡镇: "townName",
  所在区域: "full_name",
  面积: "acreage",
  图斑面积: "acreage",
  湿地面积: "acreage",
  草场面积: "acreage",
  草原类: "grassTypeName",
  草原型: "grassMinType",
  草原等级: "grasslandLevel",
  草原类型: "basic_type_name",
  小斑干草产量: "xbGcYield",
  小斑鲜草产量: "xbXcYield",
  鲜草产量: "xcYield",
  干草产量: "gcYield",
  海拔: "elevation",
  地貌: "landformName",
  权属单位: "qsdwmc",
  坐落单位: "zldwmc",
  林班: "lin_ban",
  小班: "xiao_ban",
  森林类别: "sen_lin_lb",
  林种: "lin_zhong",
  公益林等级: "gjgyl_bhdj",
  保护等级: "bh_dj",
  质量等级: "zl_dj",
  优势树种: "you_shi_sz",
  起源: "qi_yuan",
  龄组: "ling_zu",
  郁闭度: "yu_bi_du",
  林地权属: "ld_qs",
  土地使用权属: "tdsyqs",
  林木权属: "lmqs",
  生态区位: "stqwmc",
  坡度等级: "slope",
}

const mapRankingLayerKeys = new Set([
  "grassland-distribution",
  "wetland-distribution",
  "woodland-distribution",
  "woodland-protection",
  "woodland-type",
  "woodland-quality",
  "woodland-ownership",
])

const overviewLayerDefinitions = [
  { topicKey: "wetland", layerKey: "wetland-distribution", name: "湿地分布", legendName: "湿地资源" },
  { topicKey: "grassland", layerKey: "grassland-distribution", name: "草地分布", legendName: "草地资源" },
  { topicKey: "woodland", layerKey: "woodland-distribution", name: "林地分布", legendName: "林地资源" },
]

const nonBusinessFeatureKeys = new Set([
  "code",
  "msg",
  "message",
  "success",
  "data",
  "result",
  "grassland",
  "wetland",
  "woodland",
  "basicGrassland",
  "dyColumn",
  "geom",
  "wkt",
  "geometry",
  "the_geom",
  "theGeom",
  "shape",
  "shape_leng",
  "shape_length",
  "length",
  "perimeter",
  "gid",
  "id",
  "objectid",
  "object_id",
  "fid",
  "key_uid",
  "bsm",
  "ysdm",
].map((key) => key.toLowerCase()))

const activeTopic = computed(() => getTopicConfig(activeTopicKey.value))
const activeTopicLayers = computed(() => getLayersByTopic(activeTopicKey.value))
const overviewLayerOptions = computed(() => overviewLayerDefinitions
  .map((item) => createOverviewLayerOption(item))
  .filter(Boolean)
)
const layerPanelLayers = computed(() => activeTopicKey.value === "overview" ? overviewLayerOptions.value : activeTopicLayers.value)
const layerPanelValue = computed({
  get() {
    return activeTopicKey.value === "overview" ? overviewActiveLayerKeys.value : activeLayerKey.value
  },
  set(value) {
    if (activeTopicKey.value === "overview") {
      overviewActiveLayerKeys.value = Array.isArray(value) ? value.map(String).filter(Boolean) : []
      return
    }
    activeLayerKey.value = String(value || "")
  },
})
const baseActiveLayer = computed(() => activeLayerKey.value ? getLayerByKey(activeTopicKey.value, activeLayerKey.value) : null)
const ndviTimelineActive = computed(() => activeTopicKey.value === "grassland" && activeLayerKey.value === "grassland-health")
const ndviTimelineVisible = computed(() => ndviTimelineActive.value)
const selectedNdviTimelineItem = computed(() =>
  ndviTimelineRows.value.find((item) => item.key === selectedNdviTimelineKey.value) || null
)
const selectedLegend = computed(() =>
  (baseActiveLayer.value?.legends || []).find((item) => item.key === selectedLegendKey.value) || null
)
const overviewLayerLegends = computed(() => overviewLayerOptions.value.flatMap((layer) => layer.legends || []))
const activeLayerLegends = computed(() => activeTopicKey.value === "overview" ? overviewLayerLegends.value : baseActiveLayer.value?.legends || [])
const layerPanelVisible = computed(() => layerPanelLayers.value.length > 0)
const layerPanelCompact = computed(() =>
  layerPanelLayers.value.length <= 6
  && activeLayerLegends.value.length <= 8
)
const layerPanelHeight = computed(() => {
  if (!layerPanelCompact.value) return 430
  const layerCount = Math.max(1, layerPanelLayers.value.length)
  const legendRows = Math.max(1, Math.ceil(activeLayerLegends.value.length / 2))
  const layerArea = layerCount * 32
  const legendArea = 24 + legendRows * 28
  return Math.max(228, Math.min(390, 48 + layerArea + legendArea))
})
const activeLayer = computed(() => {
  if (activeTopicKey.value === "overview") {
    const selectedKeys = new Set(overviewActiveLayerKeys.value)
    const selectedLayers = overviewLayerOptions.value.filter((layer) => selectedKeys.has(layer.key))
    return createLayerStack(selectedLayers)
  }
  const layer = baseActiveLayer.value
  if (!layer) return null
  let visibleLayer = selectedLegend.value?.cqlFilter
    ? {
        ...layer,
        cqlFilter: selectedLegend.value.cqlFilter,
        activeLegend: selectedLegend.value,
        name: selectedLegend.value.name || layer.name,
      }
    : layer
  const timelineItem = selectedNdviTimelineItem.value
  if (ndviTimelineActive.value && timelineItem?.layerName) {
    visibleLayer = {
      ...visibleLayer,
      layerName: timelineItem.layerName,
      name: `${layer.name} ${timelineItem.label}`,
      timelineDate: timelineItem.label,
      timelineItem,
    }
  }
  return visibleLayer
})
const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sideCardWidth = SCREEN2_LAYOUT.sideCardWidth
const panelGap = computed(() => SCREEN2_LAYOUT.sideCardGap)
const panelHeight = computed(() => getScreen2SideCardHeight(screenViewportHeight.value))
const doublePanelHeight = computed(() => getScreen2SpanHeight(panelHeight.value, 2))
const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value))
const featurePanelHeight = computed(() => {
  if (featureLoading.value || featureEmpty.value || !selectedFeature.value) return 186
  const rowCount = estimateFeatureRowCount(selectedFeature.value)
  return Math.min(480, Math.max(300, 86 + Math.min(rowCount, 12) * 34))
})
const featurePanelStyle = computed(() => {
  if (!featurePanelPoint.value) return {}
  const panelWidth = 460
  const panelHeight = featurePanelHeight.value
  const margin = 24
  const point = toScreenLocalPoint(featurePanelPoint.value)
  const topLimit = 104
  const verticalGap = 36
  const x = Math.min(Math.max(point.x - panelWidth / 2, margin), 1920 - panelWidth - margin)
  const y = Math.min(Math.max(point.y - panelHeight - verticalGap, topLimit), 1080 - panelHeight - margin)
  return {
    left: `${x}px`,
    top: `${y}px`,
    right: "auto",
    bottom: "auto",
  }
})

function toScreenLocalPoint(point) {
  const screen = document.querySelector(".forest-grass-wet")
  const rect = screen?.getBoundingClientRect()
  if (!rect?.width || !rect?.height) return point
  return {
    x: ((point.x - rect.left) / rect.width) * 1920,
    y: ((point.y - rect.top) / rect.height) * 1080,
  }
}

function updateScreenViewportHeight() {
  screenViewportHeight.value = typeof window === "undefined" ? 1080 : window.innerHeight
}

function createOverviewLayerOption({ topicKey, layerKey, name, legendName }) {
  const layer = getLayerByKey(topicKey, layerKey)
  if (!layer || layer.key !== layerKey) return null
  const firstLegend = layer.legends?.[0] || {}
  return {
    ...layer,
    name,
    overviewTopicKey: topicKey,
    legends: [
      {
        ...firstLegend,
        key: `overview-${layerKey}`,
        name: legendName,
        selectable: false,
      },
    ],
  }
}

function createLayerStack(layers = []) {
  const validLayers = layers.filter(Boolean)
  if (!validLayers.length) return null
  const [primaryLayer, ...overlayLayers] = validLayers
  return {
    ...primaryLayer,
    overlayLayers,
  }
}

onMounted(() => {
  updateScreenViewportHeight()
  window.addEventListener("resize", updateScreenViewportHeight)
  document.addEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
  initAssets()
  loadBusinessStats(activeTopicKey.value, "")
  loadMapRanking(activeTopicKey.value, "")
})

onBeforeUnmount(() => {
  destroyed = true
  if (mapLoadTimer) window.clearTimeout(mapLoadTimer)
  window.removeEventListener("resize", updateScreenViewportHeight)
  document.removeEventListener("pointerdown", handleOutsideFeaturePointerDown, true)
})

watch(activeTopicKey, (topicKey) => {
  resettingTopicLayer = true
  selectedLegendKey.value = ""
  activeLayerKey.value = ""
  if (topicKey === "overview") overviewActiveLayerKeys.value = []
  closeFeaturePanel()
  layerStatus.value = {
    type: "idle",
    text: "等待 GeoServer 图层加载",
  }
  loadBusinessStats(topicKey, "")
  loadMapRanking(topicKey, "", null)
  nextTick(() => {
    resettingTopicLayer = false
  })
})

watch(activeLayerKey, () => {
  if (resettingTopicLayer) return
  resettingLegend = true
  selectedLegendKey.value = ""
  closeFeaturePanel()
  layerStatus.value = {
    type: activeLayerKey.value ? "loading" : "idle",
    text: activeLayerKey.value ? "正在加载 GeoServer 图层" : "等待 GeoServer 图层加载",
  }
  if (activeTopicKey.value !== "woodland") {
    loadBusinessStats(activeTopicKey.value, activeLayerKey.value)
  }
  loadMapRanking(activeTopicKey.value, activeLayerKey.value, null)
  nextTick(() => {
    resettingLegend = false
  })
})

watch(overviewActiveLayerKeys, () => {
  if (activeTopicKey.value !== "overview") return
  selectedLegendKey.value = ""
  closeFeaturePanel()
  layerStatus.value = {
    type: overviewActiveLayerKeys.value.length ? "loading" : "idle",
    text: overviewActiveLayerKeys.value.length ? "正在加载 GeoServer 图层" : "等待 GeoServer 图层加载",
  }
  mapRanking.value = createEmptyMapRanking("overview", overviewActiveLayerKeys.value.join(","))
}, { deep: true })

watch(selectedLegendKey, () => {
  if (resettingTopicLayer || resettingLegend) return
  closeFeaturePanel()
  loadMapRanking(activeTopicKey.value, activeLayerKey.value, selectedLegend.value)
})

watch(ndviTimelineActive, (active) => {
  if (active) {
    loadNdviTimeline()
    return
  }
  selectedNdviTimelineKey.value = ""
})

watch(selectedNdviTimelineKey, () => {
  if (!ndviTimelineActive.value) return
  closeFeaturePanel()
})

watch(
  () => props.active,
  (active) => {
    if (!mapSceneRef.value) return
    if (active) {
      scheduleMapLoadIfActive()
      mapSceneRef.value.resume?.()
    } else {
      mapSceneRef.value.pause?.()
    }
  },
)

function initAssets() {
  assets = new Assets()
  const params = { progress: 0 }
  assets.instance.on("onProgress", (path, itemsLoaded, itemsTotal) => {
    const value = Math.floor((itemsLoaded / itemsTotal) * 100)
    gsap.to(params, {
      progress: value,
      onUpdate: () => {
        progress.value = Math.floor(params.progress)
      },
    })
  })
  const markAssetsReady = () => {
    if (destroyed || mapAssetsReady) return
    mapAssetsReady = true
    scheduleMapLoadIfActive()
  }
  assets.instance.on("onLoad", markAssetsReady)
  assets.ready?.then(markAssetsReady).catch(() => {
    if (destroyed) return
    layerStatus.value = {
      type: "error",
      text: "地图资源加载失败",
    }
  })
}

function scheduleMapLoadIfActive() {
  if (destroyed || !props.active || !mapAssetsReady || mapLoadStarted) return
  mapLoadStarted = true
  if (mapLoadTimer) window.clearTimeout(mapLoadTimer)
  mapLoadTimer = window.setTimeout(() => {
    mapLoadTimer = null
    loadMapOnce()
  }, 600)
}

async function loadMapOnce(attempt = 0) {
  if (destroyed) return
  if (!props.active) {
    mapLoadStarted = false
    return
  }
  await nextTick()
  if (!props.active) {
    mapLoadStarted = false
    return
  }
  const mapLoaded = Boolean(mapSceneRef.value?.loadMap?.(assets) || mapSceneRef.value?.hasMap?.())
  if (mapLoaded) {
    await hideLoading()
    await nextTick()
    mapSceneRef.value?.play()
    return
  }
  if (attempt < 24) {
    window.setTimeout(() => loadMapOnce(attempt + 1), 120)
    return
  }
  mapLoadStarted = false
}

function clearMapLoadTimer() {
  if (!mapLoadTimer) return
  window.clearTimeout(mapLoadTimer)
  mapLoadTimer = null
}

function refreshMapView() {
  mapSceneRef.value?.resume?.()
  mapSceneRef.value?.refreshView?.()
}

function resetMap() {
  closeFeaturePanel()
  layerStatus.value = {
    type: "loading",
    text: "地图正在复位",
  }
  if (mapSceneRef.value?.hasMap?.()) {
    refreshMapView()
    layerStatus.value = {
      type: "success",
      text: "地图已复位",
    }
    return
  }
  clearMapLoadTimer()
  mapLoadStarted = false
  nextTick(() => {
    scheduleMapLoadIfActive()
  })
}

defineExpose({
  refreshMapView,
  resetMap,
})

async function loadBusinessStats(topicKey, layerKey = activeLayerKey.value) {
  const businessLayerKey = topicKey === "woodland" ? "" : layerKey
  const requestId = ++businessRequestId
  coreLoading.value = true
  coreStatus.value = "loading"
  const result = await getTopicBusinessStats(topicKey, businessLayerKey)
  if (destroyed || requestId !== businessRequestId) return
  if (topicKey !== activeTopicKey.value) return
  if (topicKey !== "woodland" && (businessLayerKey || "") !== (activeLayerKey.value || "")) return
  applyBusinessStats(result, topicKey)
}

async function loadNdviTimeline(force = false) {
  if (!ndviTimelineActive.value) return
  if (!force && ndviTimelineRows.value.length) {
    ensureSelectedNdviTimeline()
    return
  }
  const requestId = ++ndviTimelineRequestId
  ndviTimelineStatus.value = "loading"
  try {
    const rows = await getGrasslandNdviTimeline()
    if (destroyed || requestId !== ndviTimelineRequestId) return
    ndviTimelineRows.value = rows
    ndviTimelineStatus.value = rows.length ? "success" : "empty"
    ensureSelectedNdviTimeline()
  } catch (error) {
    if (destroyed || requestId !== ndviTimelineRequestId) return
    ndviTimelineRows.value = []
    selectedNdviTimelineKey.value = ""
    ndviTimelineStatus.value = "error"
  }
}

function ensureSelectedNdviTimeline() {
  if (!ndviTimelineRows.value.length) {
    selectedNdviTimelineKey.value = ""
    return
  }
  const existing = ndviTimelineRows.value.some((item) => item.key === selectedNdviTimelineKey.value)
  if (!existing) {
    selectedNdviTimelineKey.value = ndviTimelineRows.value[ndviTimelineRows.value.length - 1].key
  }
}

function applyBusinessStats(result, topicKey) {
  const selected = selectTopicBusinessStats(result)
  coreStats.value = selected.core || []
  businessLeft.value = selected.left || { title: "业务统计", rows: [] }
  businessRight.value = selected.right || { title: "业务统计", rows: [] }
  leftPanels.value = selected.leftPanels?.length ? selected.leftPanels : [businessLeft.value]
  rightPanels.value = selected.rightPanels?.length ? selected.rightPanels : [businessRight.value]
  coreStatus.value = selected.status
  coreLoading.value = false
  emit("topic-stats", {
    topicKey,
    topicName: getTopicConfig(topicKey).name,
    items: (selected.top?.length ? selected.top : selected.core || []).slice(0, 4),
  })
}

function selectTopicBusinessStats(result) {
  return result || {}
}

function getPanelRenderKey(panel, side) {
  return `${activeTopicKey.value}-${side}-${panel?.key || panel?.title || "panel"}`
}

function isDoublePanel(panel) {
  const rowCount = panel?.rows?.length || 0
  if (panel?.span === 1) return false
  if (panel?.span === 2) return true
  if (panel?.type === "spotlight") return false
  if (panel?.type === "metric") return false
  if (panel?.type === "column" && rowCount <= 5) return false
  if (panel?.type === "rank" && rowCount > 4) return true
  if (panel?.type === "split" && rowCount > 4) return true
  if (panel?.type === "donut" && rowCount > 5) return true
  if (panel?.type === "structure" && rowCount > 4) return true
  if (panel?.type === "bar" && rowCount > 4) return true
  if (panel?.type === "line" && rowCount > 6) return true
  return false
}

function getPanelHeight(panel) {
  return isDoublePanel(panel) ? doublePanelHeight.value : panelHeight.value
}

function estimateFeatureRowCount(feature) {
  const properties = feature?.properties || {}
  const keys = Object.keys(properties).filter((key) => isDisplayableFeatureProperty(key, properties[key]))
  return keys.length
}

function isDisplayableFeatureProperty(key, value) {
  if (value === undefined || value === null || value === "") return false
  if (Array.isArray(value) || typeof value === "object") return false
  return !nonBusinessFeatureKeys.has(String(key || "").toLowerCase())
}

function getPointQueryLayers() {
  if (activeTopicKey.value === "overview") {
    const selectedKeys = new Set(overviewActiveLayerKeys.value)
    return overviewLayerOptions.value
      .filter((layer) => selectedKeys.has(layer.key))
      .slice()
      .reverse()
  }
  return baseActiveLayer.value ? [baseActiveLayer.value] : []
}

function getPointQueryType(layer = {}) {
  const topicKey = String(layer.overviewTopicKey || activeTopicKey.value || "").toLowerCase()
  const signal = `${layer.key || ""} ${layer.layerName || ""} ${topicKey}`.toLowerCase()
  if (signal.includes("basic-grassland") || signal.includes("t_basic_grassland")) return "basicGrassland"
  if (topicKey === "woodland" || signal.includes("woodland") || signal.includes("t_woodland")) return "woodland"
  if (topicKey === "wetland" || signal.includes("wetland") || signal.includes("shidi")) return "wetland"
  if (topicKey === "grassland" || signal.includes("grassland") || signal.includes("caodi")) return "grassland"
  return ""
}

function buildPointWkt(lngLat = {}) {
  const lng = formatWktCoordinate(lngLat.lng)
  const lat = formatWktCoordinate(lngLat.lat)
  if (lng === "" || lat === "") return ""
  return `POINT(${lng} ${lat})`
}

function formatWktCoordinate(value) {
  const number = Number(value)
  if (!Number.isFinite(number)) return ""
  return number.toFixed(8).replace(/\.?0+$/, "")
}

async function handleMapClick(payload = {}) {
  const lng = Number(payload?.lngLat?.lng)
  const lat = Number(payload?.lngLat?.lat)
  const queryLayers = getPointQueryLayers()
  if (!Number.isFinite(lng) || !Number.isFinite(lat) || !queryLayers.length) {
    closeFeaturePanel()
    return
  }

  const requestId = ++featureQueryRequestId
  const clickScreen = payload.screen || { x: 960, y: 540 }
  featurePanelPoint.value = null
  selectedFeature.value = null
  featureEmpty.value = false
  featureLoading.value = true
  mapSceneRef.value?.clearSelectedFeature?.()

  try {
    for (const layer of queryLayers) {
      const feature = await queryMagicPointFeature(layer, { lng, lat })
      if (requestId !== featureQueryRequestId || destroyed) return
      if (feature) {
        selectedFeature.value = feature
        featurePanelPoint.value = clickScreen
        featureEmpty.value = false
        mapSceneRef.value?.highlightFeature?.(feature)
        return
      }
    }
    if (requestId === featureQueryRequestId) closeFeaturePanel()
  } catch (error) {
    if (requestId === featureQueryRequestId) closeFeaturePanel()
  } finally {
    if (requestId === featureQueryRequestId) {
      featureLoading.value = false
    }
  }
}

async function queryMagicPointFeature(layer, lngLat) {
  const queryType = getPointQueryType(layer)
  if (!queryType) return null
  const wkt = buildPointWkt(lngLat)
  let detail = null
  if (queryType === "grassland") {
    detail = await getLandClickInfo({ wkt })
  } else if (queryType === "wetland") {
    detail = await getWetlandClickInfo({ wkt })
  } else if (queryType === "woodland") {
    detail = await getWoodlandClickInfoByWkt(wkt)
  } else if (queryType === "basicGrassland") {
    detail = await getBasicGrasslandClickInfoByWkt(wkt)
  }
  const item = pickMagicPointDetail(detail, queryType)
  if (!hasMagicPointDetail(item)) return null
  const feature = normalizeMagicPointFeature(item, layer, lngLat, queryType)
  return estimateFeatureRowCount(feature) > 0 ? feature : null
}

function pickMagicPointDetail(payload, queryType) {
  const candidates = [
    payload?.[queryType],
    payload?.grassland,
    payload?.wetland,
    payload?.woodland,
    payload?.basicGrassland,
    payload?.data?.[queryType],
    payload?.data?.grassland,
    payload?.data?.wetland,
    payload?.data?.woodland,
    payload?.data?.basicGrassland,
    payload?.data,
    payload?.result,
    payload,
  ]
  for (const candidate of candidates) {
    const item = pickFirstObject(candidate)
    if (item) return item
  }
  return null
}

function pickFirstObject(value) {
  if (Array.isArray(value)) return value.find((item) => item && typeof item === "object") || null
  return value && typeof value === "object" ? value : null
}

function hasMagicPointDetail(detail) {
  if (!detail || typeof detail !== "object") return false
  if (Array.isArray(detail.dyColumn) && detail.dyColumn.some(hasMagicColumnValue)) return true
  return Object.keys(detail).some((key) => isDisplayableFeatureProperty(key, detail[key]))
}

function hasMagicColumnValue(column = {}) {
  const value = column?.value
  if (value === undefined || value === null || value === "") return false
  return Boolean(String(column?.field || column?.title || "").trim())
}

function normalizeMagicPointFeature(detail, layer, lngLat, queryType) {
  const properties = normalizeMagicPointProperties(detail)
  const geometry = pickMagicGeometry(detail)
  return {
    id: detail.id || detail.patchNo || detail.patch_no || detail.landNumber || detail.land_number || `${queryType}-${lngLat.lng}-${lngLat.lat}`,
    properties,
    feature: {
      type: "Feature",
      properties,
      geometry,
    },
    geometry,
    layer,
    lngLat,
  }
}

function pickMagicGeometry(detail = {}) {
  return detail.geometry
    || detail.geom
    || detail.wkt
    || detail.the_geom
    || detail.theGeom
    || detail.geo
    || detail.geometryWkt
    || detail.geometry_wkt
    || detail.shape
    || null
}

function normalizeMagicPointProperties(detail = {}) {
  const properties = { ...detail }
  const dyColumns = Array.isArray(detail.dyColumn) ? detail.dyColumn : []
  dyColumns.forEach((column) => {
    const value = column?.value
    if (value === undefined || value === null || value === "") return
    const titleKey = magicDetailTitleFieldMap[String(column?.title || "").trim()]
    const fieldKey = String(column?.field || "").trim()
    const keys = [fieldKey, camelToSnake(fieldKey), titleKey].filter(Boolean)
    keys.forEach((key) => {
      if (properties[key] === undefined || properties[key] === null || properties[key] === "") {
        properties[key] = value
      }
    })
  })
  return properties
}

function camelToSnake(value) {
  return String(value || "").replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`)
}

async function loadMapRanking(topicKey, layerKey, legend = selectedLegend.value) {
  const requestId = ++rankingRequestId
  if (!layerKey || !shouldShowMapRanking(layerKey, legend)) {
    mapRanking.value = createEmptyMapRanking(topicKey, layerKey)
    return
  }
  const result = await getLayerTownRanking(topicKey, layerKey, legend)
  if (destroyed || requestId !== rankingRequestId) return
  if (topicKey !== activeTopicKey.value || layerKey !== activeLayerKey.value) return
  if ((legend?.key || "") !== selectedLegendKey.value) return
  mapRanking.value = {
    ...result,
    hideTownLabelStats: true,
  }
}

function shouldShowMapRanking(layerKey, legend = null) {
  return Boolean(legend?.key) || mapRankingLayerKeys.has(layerKey)
}

function createEmptyMapRanking(topicKey, layerKey) {
  return {
    status: "disabled",
    topicKey,
    layerKey,
    title: "",
    unit: "",
    hideTownLabelStats: true,
    rows: [],
  }
}

function handleFeatureInfo(payload) {
  const feature = payload?.feature ?? payload ?? null
  if (!feature || estimateFeatureRowCount(feature) <= 0) {
    closeFeaturePanel()
    return
  }
  selectedFeature.value = feature
  featureEmpty.value = false
  if (payload?.screen) {
    featurePanelPoint.value = payload.screen
  }
}

function handleLayerStatus(status) {
  layerStatus.value = status
}

function closeFeaturePanel() {
  featureQueryRequestId += 1
  selectedFeature.value = null
  featureEmpty.value = false
  featureLoading.value = false
  featurePanelPoint.value = null
  mapSceneRef.value?.clearSelectedFeature?.()
}

function handleOutsideFeaturePointerDown(event) {
  if (!featurePanelVisible.value) return
  const panel = document.querySelector(".fgw-map-feature-info")
  const target = event.target
  if (panel && target instanceof Node && panel.contains(target)) return
  closeFeaturePanel()
}

function hideLoading() {
  if (!loadingVisible.value) return Promise.resolve()
  return new Promise((resolve) => {
    gsap.to(".forest-grass-wet .loading", {
      opacity: 0,
      duration: 0.8,
      delay: 0.2,
      onComplete: () => {
        loadingVisible.value = false
        resolve()
      },
    })
  })
}
</script>

<style lang="scss">
.forest-grass-wet {
  position: absolute;
  inset: 0;
  height: 100%;
  overflow: clip;
  overscroll-behavior: none;

  > .map {
    width: 100%;
    height: 100%;

    > canvas {
      display: block;
      width: 100% !important;
      height: 100% !important;
    }

    > div {
      width: 100% !important;
      height: 100% !important;
    }
  }

  &-ui {
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
  }

  .fgw-left,
  .fgw-right {
    position: absolute;
    top: 128px;
    bottom: 8px;
    z-index: 6;
    width: 398px;
    perspective-origin: 50% 50%;
    pointer-events: all;
  }

  .fgw-left {
    left: 32px;
    perspective: var(--screen-side-perspective-left, 500px);
  }

  .fgw-right {
    right: 32px;
    perspective: var(--screen-side-perspective-right, 800px);
  }

  .fgw-side-stack {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
    transform-style: preserve-3d;

    &.is-left {
      transform: translate3d(0, 0, 0) rotateY(6deg);
      transform-origin: left center;
    }

    &.is-right {
      transform: translate3d(0, 0, 0) rotateY(-6deg);
      transform-origin: right center;
    }
  }

  .fgw-side-card {
    flex: 0 0 auto;

    .m-card-bd-content {
      top: 36px;
      bottom: 0;
      color: #f3feff;
    }

  }

  .fgw-map-layer-tools,
  .fgw-map-feature-info {
    position: absolute;
    z-index: 7;
    pointer-events: all;

    .m-card {
      background: rgba(3, 18, 30, 0.54);
      backdrop-filter: blur(2px);
    }
  }

  .fgw-map-layer-tools {
    top: auto;
    right: 458px;
    left: auto;
    bottom: 72px;
    width: 248px;
    height: 430px;

    .m-card {
      width: 100% !important;
      height: 100% !important;
    }
  }

  .fgw-ndvi-timeline {
    position: absolute;
    left: 50%;
    bottom: 112px;
    z-index: 7;
    transform: translateX(-50%);
    pointer-events: all;
  }

  .fgw-map-feature-info {
    right: 456px;
    bottom: 150px;
  }

  .fgw-bottom-topic {
    position: absolute;
    left: 50%;
    bottom: 48px;
    z-index: 8;
    transform: translateX(-50%);
    pointer-events: all;
  }
}
</style>
