<template>
  <div class="forest-grass-wet ecological-protection">
    <map-scene
      ref="mapSceneRef"
      :active-layer="activeLayer"
      :map-ranking="mapRanking"
      :admin-boundary-line-scale="1.65"
      :auto-apply-layer-map-view="false"
      :max-zoom="21"
      @feature-loading="featureLoading = $event"
      @feature-info="handleFeatureInfo"
      @map-click="handleMapClick"
      @layer-status="handleLayerStatus"
    />

    <div class="forest-grass-wet-ui ecological-protection-ui">
      <div v-if="!isEngineeringAnalysis" 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) },
              `ecological-side-card--${panel.key}`,
            ]"
            :title="panel.title"
            :width="sideCardWidth"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer
              :panel="panel"
              compact
              column-label-standard
              visual-version="v21"
              @row-select="handlePanelRowSelect(panel, $event)"
            />
          </m-card>
        </div>
      </div>

      <div v-if="!isEngineeringAnalysis" 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) },
              `ecological-side-card--${panel.key}`,
            ]"
            :title="panel.title"
            :width="sideCardWidth"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer
              :panel="panel"
              compact
              column-label-standard
              visual-version="v21"
              @row-select="handlePanelRowSelect(panel, $event)"
            />
          </m-card>
        </div>
      </div>

      <engineering-analysis-panel
        v-if="isEngineeringAnalysis"
        @analysis-reset="handleEngineeringAnalysisReset"
        @analysis-batch="handleEngineeringAnalysisBatch"
        @analysis-complete="handleEngineeringAnalysisComplete"
        @result-visibility="handleEngineeringResultVisibility"
      />

      <engineering-statistics-panel
        v-if="isEngineeringAnalysis && engineeringStatistics"
        :statistics="engineeringStatistics"
      />

      <div
        class="fgw-map-layer-tools"
        :class="{
          'is-analysis': isEngineeringAnalysis,
          'is-compact': layerPanelCompact,
        }"
        :style="{ height: `${layerPanelHeight}px` }"
      >
        <layer-panel
          v-model="activeLayerKey"
          v-model:selected-legend-key="selectedLegendKey"
          :topic-key="currentView"
          :legends="activeLayerLegends"
          :width="248"
          :height="layerPanelHeight"
          :compact="layerPanelCompact"
          :show-legend="!isEngineeringAnalysis"
        />
      </div>

      <div v-if="featurePanelVisible" class="fgw-map-feature-info" :style="featurePanelStyle">
        <feature-info-panel
          :title="featurePanelTitle"
          :feature="selectedFeature"
          :empty="featureEmpty"
          :loading="featureLoading"
          :width="featurePanelWidth"
          :height="featurePanelHeight"
          closable
          @close="closeFeaturePanel"
        />
      </div>

      <div class="fgw-bottom-topic ecological-bottom-topic">
        <topic-switcher
          :model-value="currentView"
          :topics="ecologicalProtectionTopics"
          @update:model-value="handleViewSelect"
        />
      </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 "@/views/forestGrassWet/layout.scss"
import mCard from "@/components/mCard/index.vue"
import {
  ecologicalProtectionTopics,
  getLayerByKey,
  getLayersByTopic,
} from "@/config/layers"
import { getEcologicalScopeStats } from "@/services/ecologicalProtectionApi"
import { getProjectClickInfoByWkt } from "@/services/pastureApi"
import { Assets } from "@/views/forestGrassWet/assets"
import { SCREEN2_LAYOUT, getScreen2SideCardHeight, getScreen2SpanHeight } from "@/utils/screen2Standard"
import { createFeaturePanelLayout, FEATURE_PANEL_WIDTH } from "@/utils/featurePanelLayout"
import MapScene from "@/components/CesiumMap/index.vue"
import LayerPanel from "@/views/forestGrassWet/components/LayerPanel.vue"
import FeatureInfoPanel from "@/views/forestGrassWet/components/FeatureInfoPanel.vue"
import BusinessPanelRenderer from "@/views/forestGrassWet/components/BusinessPanelRenderer.vue"
import TopicSwitcher from "@/views/forestGrassWet/components/TopicSwitcher.vue"
import EngineeringAnalysisPanel from "./components/EngineeringAnalysisPanel.vue"
import EngineeringStatisticsPanel from "./components/EngineeringStatisticsPanel.vue"
import {
  buildEcologicalScopePanels,
  ecologicalScopeViewKeys,
  engineeringAnalysisPanels,
  getEcologicalScopeTopStats,
  getEcologicalMapRanking,
  getEcologicalTopStats,
  parkPanels,
  restorationPanels,
} from "./data"

const props = defineProps({
  activeView: {
    type: String,
    default: "ecological-redline",
  },
  showLoading: {
    type: Boolean,
    default: true,
  },
  active: {
    type: Boolean,
    default: true,
  },
})

const emit = defineEmits(["module-stats", "view-change"])
const mapSceneRef = ref(null)
const activeLayerKey = ref("")
const selectedLegendKey = ref("")
const scopePanels = ref(buildEcologicalScopePanels({}, "ecological-redline"))
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 mapRanking = ref(null)
const layerStatus = ref({
  type: "idle",
  text: "等待生态保护图层加载",
})
const engineeringStatistics = ref(null)

let assets = null
let destroyed = false
let mapAssetsReady = false
let mapLoadStarted = false
let mapLoadTimer = null
let scopeStatsRequestId = 0
let featureQueryRequestId = 0
const scopeStatsCache = new Map()

const nonBusinessFeatureKeys = new Set([
  "code",
  "msg",
  "message",
  "success",
  "data",
  "result",
  "project",
  "progress",
  "dycolumn",
  "geom",
  "wkt",
  "geometry",
  "the_geom",
  "raw_properties",
  "gid",
  "id",
  "objectid",
  "object_id",
  "fid",
  "key_uid",
  "bsm",
  "ysdm",
])

const currentView = computed(() =>
  ecologicalProtectionTopics.some((item) => item.key === props.activeView) ? props.activeView : "ecological-redline"
)
const isEngineeringAnalysis = computed(() => currentView.value === "engineering-analysis")
const isProjectDetailView = computed(() => ["restoration", "engineering-analysis"].includes(currentView.value))
const isEcologicalScopeView = computed(() => ecologicalScopeViewKeys.includes(currentView.value))
const baseActiveLayer = computed(() => activeLayerKey.value ? getLayerByKey(currentView.value, activeLayerKey.value) : null)
const selectedLegend = computed(() =>
  (baseActiveLayer.value?.legends || []).find((item) => item.key === selectedLegendKey.value) || null
)
const activeLayerLegends = computed(() => baseActiveLayer.value?.legends || [])
const currentLayerOptions = computed(() => getLayersByTopic(currentView.value))
const layerPanelCompact = computed(() =>
  currentLayerOptions.value.length <= 2
  && activeLayerLegends.value.length <= 6
)
const layerPanelHeight = computed(() => {
  if (isEngineeringAnalysis.value) return 112
  if (!layerPanelCompact.value) return 430
  const layerRows = Math.max(1, currentLayerOptions.value.length)
  const legendRows = Math.max(1, Math.ceil(activeLayerLegends.value.length / 2))
  const layerArea = layerRows * 34 + Math.max(0, layerRows - 1) * 5
  const legendArea = 30 + legendRows * 28
  return Math.max(196, Math.min(340, 72 + layerArea + legendArea))
})
const activeLayer = computed(() => {
  const layer = baseActiveLayer.value
  if (!layer) return null
  return selectedLegend.value
    ? {
        ...layer,
        cqlFilter: selectedLegend.value.cqlFilter || layer.cqlFilter,
        activeLegend: selectedLegend.value,
        name: selectedLegend.value.name || layer.name,
      }
    : layer
})
const currentPanels = computed(() => {
  if (isEcologicalScopeView.value) return scopePanels.value
  if (currentView.value === "restoration") return restorationPanels
  if (currentView.value === "engineering-analysis") return engineeringAnalysisPanels
  return parkPanels
})
const leftPanels = computed(() => currentPanels.value.left || [])
const rightPanels = computed(() => currentPanels.value.right || [])
const screenViewportHeight = ref(typeof window === "undefined" ? 1080 : window.innerHeight)
const sideCardWidth = SCREEN2_LAYOUT.sideCardWidth
const panelHeight = computed(() => getScreen2SideCardHeight(screenViewportHeight.value))
const doublePanelHeight = computed(() => getScreen2SpanHeight(panelHeight.value, 2))
const featurePanelVisible = computed(() => Boolean(featurePanelPoint.value))
const featurePanelWidth = FEATURE_PANEL_WIDTH
const featurePanelLayout = computed(() => createFeaturePanelLayout({
  point: featurePanelPoint.value ? toScreenLocalPoint(featurePanelPoint.value) : null,
  rowCount: selectedFeature.value ? estimateFeatureRowCount(selectedFeature.value) : 0,
  loading: featureLoading.value,
  empty: featureEmpty.value || !selectedFeature.value,
}))
const featurePanelHeight = computed(() => featurePanelLayout.value.height)
const featurePanelStyle = computed(() => featurePanelLayout.value.style)
const featurePanelTitle = computed(() => {
  const titleMap = {
    "ecological-redline": "生态保护红线详情",
    "natural-protected-area": "自然保护地详情",
    "national-park": "国家公园详情",
    restoration: "生态修复详情",
    "engineering-analysis": "生态工程详情",
  }
  return titleMap[currentView.value] || `${baseActiveLayer.value?.name || "生态图斑"}详情`
})

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

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

watch(currentView, (viewKey) => {
  syncView(viewKey)
})

watch(activeLayerKey, () => {
  selectedLegendKey.value = ""
  closeFeaturePanel()
})

watch(selectedLegendKey, () => {
  closeFeaturePanel()
})

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

function syncView(viewKey) {
  const topic = ecologicalProtectionTopics.find((item) => item.key === viewKey) || ecologicalProtectionTopics[0]
  const layers = getLayersByTopic(viewKey)
  engineeringStatistics.value = null
  selectedLegendKey.value = ""
  activeLayerKey.value = topic.defaultLayerKey || layers[0]?.key || ""
  mapRanking.value = getEcologicalMapRanking(viewKey)
  mapSceneRef.value?.setBoundaryDrawing?.(false)
  mapSceneRef.value?.clearAnalysisBoundary?.()
  if (viewKey !== "engineering-analysis") mapSceneRef.value?.clearEngineeringSuitability?.()
  closeFeaturePanel()
  if (ecologicalScopeViewKeys.includes(viewKey)) {
    scopePanels.value = buildEcologicalScopePanels({}, viewKey)
    loadEcologicalScopeStats(viewKey)
  }
  emit("module-stats", {
    moduleKey: "ecological-protection",
    viewKey,
    items: ecologicalScopeViewKeys.includes(viewKey)
      ? getEcologicalScopeTopStats({}, viewKey)
      : getEcologicalTopStats(viewKey),
  })
  focusCurrentLayerMapView()
}

function focusCurrentLayerMapView(attempt = 0) {
  const layer = activeLayer.value || baseActiveLayer.value
  if (!layer?.mapView && !layer?.cameraPosition) return
  nextTick(() => {
    const applied = mapSceneRef.value?.focusLayerMapView?.(layer)
    if (!applied && attempt < 12) {
      window.setTimeout(() => focusCurrentLayerMapView(attempt + 1), 120)
    }
  })
}

function handleViewSelect(viewKey) {
  const nextKey = ecologicalProtectionTopics.some((item) => item.key === viewKey) ? viewKey : "ecological-redline"
  if (nextKey === currentView.value) return
  emit("view-change", nextKey)
}

async function loadEcologicalScopeStats(viewKey) {
  const requestId = ++scopeStatsRequestId
  try {
    const cached = scopeStatsCache.get(viewKey)
    const stats = cached || await getEcologicalScopeStats(viewKey)
    if (requestId !== scopeStatsRequestId || currentView.value !== viewKey) return
    scopeStatsCache.set(viewKey, stats)
    scopePanels.value = buildEcologicalScopePanels(stats, viewKey)
    emit("module-stats", {
      moduleKey: "ecological-protection",
      viewKey,
      items: getEcologicalScopeTopStats(stats, viewKey),
    })
  } catch (error) {
    if (requestId !== scopeStatsRequestId || currentView.value !== viewKey) return
    scopePanels.value = buildEcologicalScopePanels({}, viewKey)
    emit("module-stats", {
      moduleKey: "ecological-protection",
      viewKey,
      items: getEcologicalScopeTopStats({}, viewKey),
    })
    layerStatus.value = {
      type: "error",
      text: "保护范围统计加载失败",
    }
    console.warn("Failed to load ecological scope statistics", error)
  }
}

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()
    focusCurrentLayerMapView()
    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()
  mapSceneRef.value?.setBoundaryDrawing?.(false)
  mapSceneRef.value?.clearAnalysisBoundary?.()
  if (mapSceneRef.value?.hasMap?.()) {
    refreshMapView()
    return
  }
  clearMapLoadTimer()
  mapLoadStarted = false
  nextTick(() => {
    scheduleMapLoadIfActive()
  })
}

defineExpose({
  refreshMapView,
  resetMap,
})

function toScreenLocalPoint(point) {
  const screen = document.querySelector(".ecological-protection")
  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 getPanelRenderKey(panel, side) {
  return `${currentView.value}-${side}-${panel?.key || panel?.title || "panel"}`
}

function isDoublePanel(panel) {
  return panel?.span === 2
}

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

function estimateFeatureRowCount(feature) {
  const properties = feature?.properties || {}
  return Object.keys(properties).filter((key) => isDisplayableFeatureProperty(key, properties[key])).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 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 = {}) {
  if (!isProjectDetailView.value) return
  if (isEngineeringAnalysis.value) return
  if (isVectorProjectLayer(payload.overlayLayer || activeLayer.value || baseActiveLayer.value)) {
    closeFeaturePanel()
    return
  }
  const lng = Number(payload?.lngLat?.lng)
  const lat = Number(payload?.lngLat?.lat)
  if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
    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

  try {
    const detail = await getProjectClickInfoByWkt(buildPointWkt({ lng, lat }))
    if (requestId !== featureQueryRequestId || destroyed) return
    const feature = normalizeProjectClickFeature(detail, payload)
    if (!feature || estimateFeatureRowCount(feature) <= 0) {
      closeFeaturePanel()
      return
    }
    selectedFeature.value = feature
    featurePanelPoint.value = mapSceneRef.value?.getVectorFeatureAnchorScreen?.(feature) || clickScreen
    featureEmpty.value = false
    mapSceneRef.value?.highlightFeature?.(feature, { highlightTone: "v21" })
  } catch (error) {
    if (requestId !== featureQueryRequestId) return
    closeFeaturePanel()
  } finally {
    if (requestId === featureQueryRequestId) {
      featureLoading.value = false
    }
  }
}

function isVectorProjectLayer(layer = activeLayer.value) {
  const sourceType = String(layer?.sourceType || "").toLowerCase()
  return ["geojson", "wkt-api", "wfs"].includes(sourceType)
    || String(layer?.dataPath || "").includes("ecological_restoration_projects")
}

function normalizeProjectClickFeature(payload, clickPayload = {}) {
  const project = pickFirstObject(payload?.project || payload?.data?.project || payload?.data || payload)
  if (!hasProjectDetail(project)) return null
  const properties = normalizeProjectProperties(project, payload)
  const geometry = project.geom || project.geometry || project.wkt || null
  return {
    id: project.id || properties.id || properties.project_name || `project-${clickPayload?.lngLat?.lng}-${clickPayload?.lngLat?.lat}`,
    properties,
    feature: {
      type: "Feature",
      properties,
      geometry,
    },
    geometry,
    layer: clickPayload.overlayLayer || activeLayer.value || baseActiveLayer.value,
    lngLat: clickPayload.lngLat,
  }
}

function normalizePickedProjectFeature(payload, clickPayload = {}) {
  const source = payload?.feature && payload.feature.type === "Feature"
    ? {
        ...payload.feature,
        id: payload.id || payload.feature.id,
        layer: payload.layer,
        lngLat: payload.lngLat,
      }
    : payload
  const sourceFeature = source?.type === "Feature" ? source : source?.feature?.type === "Feature" ? source.feature : null
  const rawProperties = source?.properties || sourceFeature?.properties || {}
  const geometry = source?.geometry
    || sourceFeature?.geometry
    || rawProperties.geometry
    || rawProperties.geom
    || rawProperties.wkt
    || null
  const properties = normalizeProjectProperties(rawProperties, clickPayload)
  const id = source?.id
    || sourceFeature?.id
    || properties.id
    || properties.gid
    || properties.project_id
    || properties.project_name
    || `project-${clickPayload?.lngLat?.lng || ""}-${clickPayload?.lngLat?.lat || ""}`
  return {
    id,
    properties,
    feature: {
      type: "Feature",
      id,
      properties,
      geometry,
    },
    geometry,
    layer: source?.layer || clickPayload.overlayLayer || activeLayer.value || baseActiveLayer.value,
    lngLat: source?.lngLat || clickPayload.lngLat,
  }
}

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

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

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

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

  const projectName = properties.project_name || properties.projectName || properties.name
  const acreage = properties.area_mu || properties.areaMu || properties.acreage
  const projectStatus = properties.project_status || properties.projectStatus
  return {
    ...properties,
    project_name: projectName,
    name: projectName || properties.name,
    area_mu: acreage,
    project_status: projectStatus,
    progress_count: Array.isArray(payload?.progress) ? payload.progress.length : undefined,
  }
}

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

function handleFeatureInfo(payload) {
  const feature = payload?.feature ?? payload ?? null
  if (!feature || estimateFeatureRowCount(feature) <= 0) {
    closeFeaturePanel()
    return
  }
  if (isProjectDetailView.value) {
    featureQueryRequestId += 1
    const projectFeature = normalizePickedProjectFeature(feature, payload)
    if (!projectFeature || estimateFeatureRowCount(projectFeature) <= 0) {
      closeFeaturePanel()
      return
    }
    selectedFeature.value = projectFeature
    featureEmpty.value = false
    featureLoading.value = false
    featurePanelPoint.value = mapSceneRef.value?.getVectorFeatureAnchorScreen?.(projectFeature) || payload?.screen || null
    return
  }
  selectedFeature.value = feature
  featureEmpty.value = false
  if (payload?.screen) {
    featurePanelPoint.value = payload.screen
  }
}

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

function handlePanelRowSelect(panel, row) {
  if (currentView.value === "national-park" && row?.name) {
    closeFeaturePanel()
    mapSceneRef.value?.focusTown?.(row.name, {
      distanceScale: 0.66,
      targetLocalZ: 0.84,
      cameraOffset: { x: 0, y: 1.05, z: 0 },
      select: true,
    })
    return
  }
  if (currentView.value !== "restoration" || panel?.key !== "restoration-projects") return
  const projectName = row?.projectName || row?.name
  if (!projectName) return
  closeFeaturePanel()
  const focus = () => scheduleRestorationProjectFocus(row)
  if (selectedLegendKey.value) {
    selectedLegendKey.value = ""
    nextTick(() => window.setTimeout(focus, 80))
    return
  }
  focus()
}

function scheduleRestorationProjectFocus(row, attempt = 0) {
  if (currentView.value !== "restoration") return
  const projectName = row?.projectName || row?.name
  if (!projectName) return
  const result = mapSceneRef.value?.focusProject?.({
    id: row?.id,
    key: row?.key,
    name: projectName,
    projectName,
    project_name: projectName,
    highlightTone: "white",
  })
  if (!result?.feature) {
    if (attempt < 10) {
      window.setTimeout(() => scheduleRestorationProjectFocus(row, attempt + 1), 120)
      return
    }
    layerStatus.value = {
      type: "warning",
      text: "当前项目图斑尚未加载",
    }
    return
  }
  const projectFeature = normalizePickedProjectFeature({
    id: result.feature.id,
    properties: result.feature.properties || {},
    geometry: result.feature.geometry,
    feature: result.feature,
    layer: activeLayer.value || baseActiveLayer.value,
  }, {})
  selectedFeature.value = projectFeature
  featureEmpty.value = false
  featureLoading.value = false
  featurePanelPoint.value = result.screen
    || mapSceneRef.value?.getVectorFeaturesAnchorScreen?.(result.highlightFeatures?.length ? result.highlightFeatures : result.features)
    || mapSceneRef.value?.getVectorFeatureAnchorScreen?.(projectFeature)
}

function handleEngineeringAnalysisReset() {
  closeFeaturePanel()
  engineeringStatistics.value = null
  mapSceneRef.value?.clearEngineeringSuitability?.()
}

function handleEngineeringAnalysisBatch(payload = {}) {
  mapSceneRef.value?.addEngineeringSuitabilityBatch?.(payload)
}

function handleEngineeringAnalysisComplete(payload = {}) {
  engineeringStatistics.value = payload.statistics || null
  mapSceneRef.value?.completeEngineeringSuitability?.(payload)
}

function handleEngineeringResultVisibility(payload = {}) {
  mapSceneRef.value?.setEngineeringSuitabilityVisibility?.(payload.key, payload.visible)
}

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(".ecological-protection .loading", {
      opacity: 0,
      duration: 0.8,
      delay: 0.2,
      onComplete: () => {
        loadingVisible.value = false
        resolve()
      },
    })
  })
}
</script>

<style lang="scss">
.ecological-protection {
  .fgw-map-layer-tools {
    top: auto;
    right: 458px;
    bottom: 132px;
    left: auto;
    width: 248px;
    height: 430px;

    &.is-analysis {
      top: auto;
      right: 458px;
      bottom: 132px;
      left: auto;
      transform: none;
      transform-origin: right top;
    }

    &.is-compact {
      height: auto;
    }
  }

  .ecological-bottom-topic {
    bottom: 48px;

    .topic-switcher {
      width: max-content;
    }

    .topic-switcher-item {
      flex-basis: 120px;
      width: 120px;
    }

    .topic-name {
      font-size: 13px;
      letter-spacing: 0;
    }
  }
}
</style>
