<template>
  <div class="forest-grass-wet ecological-protection">
    <map-scene
      ref="mapSceneRef"
      :active-layer="activeLayer"
      :map-ranking="mapRanking"
      :admin-boundary-line-scale="2.4"
      @feature-loading="featureLoading = $event"
      @feature-info="handleFeatureInfo"
      @layer-status="handleLayerStatus"
      @draw-boundary="handleDrawBoundary"
    />

    <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) }"
            :title="panel.title"
            :width="398"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer :panel="panel" compact @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) }"
            :title="panel.title"
            :width="398"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer :panel="panel" compact @row-select="handlePanelRowSelect(panel, $event)" />
          </m-card>
        </div>
      </div>

      <engineering-analysis-panel
        v-if="isEngineeringAnalysis"
        ref="engineeringAnalysisRef"
        :drawing-point-count="drawingPointCount"
        @drawing-change="handleDrawingChange"
        @finish-drawing="handleFinishDrawing"
        @undo-point="handleUndoDrawingPoint"
        @clear-boundary="handleClearAnalysisBoundary"
        @analysis-result="handleAnalysisResult"
        @focus-project="handleAnalysisProjectFocus"
      />

      <div
        class="fgw-map-layer-tools"
        :class="{
          'is-analysis': isEngineeringAnalysis,
          'is-national-park': isNationalPark,
        }"
      >
        <layer-panel
          v-model="activeLayerKey"
          v-model:selected-legend-key="selectedLegendKey"
          :topic-key="currentView"
          :legends="activeLayer?.legends || []"
          :width="layerPanelWidth"
          :height="layerPanelHeight"
          :legend-only="isNationalPark"
        />
      </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 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 mCard from "@/components/mCard/index.vue"
import { ecologicalProtectionTopics, getLayerByKey, getLayersByTopic } from "@/config/layers"
import { Assets } from "@/views/forestGrassWet/assets"
import MapScene from "@/views/forestGrassWet/map.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 {
  engineeringAnalysisPanels,
  getEcologicalMapRanking,
  getEcologicalTopStats,
  parkPanels,
  restorationPanels,
} from "./data"

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

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

let assets = null
let destroyed = false
let mapAssetsReady = false
let mapLoadStarted = false
let mapLoadTimer = null

const currentView = computed(() =>
  ["national-park", "restoration", "engineering-analysis"].includes(props.activeView) ? props.activeView : "national-park"
)
const isNationalPark = computed(() => currentView.value === "national-park")
const isEngineeringAnalysis = computed(() => currentView.value === "engineering-analysis")
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 activeLayer = computed(() => {
  const layer = baseActiveLayer.value
  if (!layer) return layer
  if (!selectedLegend.value) return layer
  return {
    ...layer,
    cqlFilter: selectedLegend.value.cqlFilter || layer.cqlFilter,
    activeLegend: selectedLegend.value,
    name: selectedLegend.value.name || layer.name,
  }
})
const currentPanels = computed(() => {
  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 sidePanelTop = 128
const sidePanelGap = 12
const sidePanelBottomGap = 8
const sidePanelMinHeight = 227
const panelHeight = computed(() => {
  const viewportHeight = Math.max(1080, Number(screenViewportHeight.value) || 1080)
  const fittedHeight = Math.floor((viewportHeight - sidePanelTop - sidePanelBottomGap - sidePanelGap * 3) / 4)
  return Math.max(sidePanelMinHeight, fittedHeight)
})
const panelGap = 12
const doublePanelHeight = computed(() => panelHeight.value * 2 + panelGap)
const layerPanelWidth = computed(() => isNationalPark.value ? 178 : 248)
const layerPanelHeight = computed(() => isNationalPark.value ? 118 : 430)
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 xOffset = point.x > 1420 ? -panelWidth - 28 : 28
  const x = Math.min(Math.max(point.x + xOffset, margin), 1920 - panelWidth - margin)
  const y = Math.min(Math.max(point.y - panelHeight - 72, 188), 1080 - panelHeight - 132)
  return {
    left: `${x}px`,
    top: `${y}px`,
    right: "auto",
    bottom: "auto",
  }
})

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)
  selectedLegendKey.value = ""
  activeLayerKey.value = topic.defaultLayerKey || layers[0]?.key || ""
  mapRanking.value = getEcologicalMapRanking(viewKey)
  drawingPointCount.value = 0
  mapSceneRef.value?.setBoundaryDrawing?.(false)
  if (viewKey !== "engineering-analysis") mapSceneRef.value?.clearAnalysisBoundary?.()
  closeFeaturePanel()
  emit("module-stats", {
    moduleKey: "ecological-protection",
    viewKey,
    items: getEcologicalTopStats(viewKey),
  })
}

function handleViewSelect(viewKey) {
  const nextKey = ["national-park", "restoration", "engineering-analysis"].includes(viewKey) ? viewKey : "national-park"
  if (nextKey === currentView.value) return
  emit("view-change", nextKey)
}

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()
  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) => {
    const value = properties[key]
    if (value === undefined || value === null || value === "") return false
    return !["geom", "wkt", "geometry", "the_geom", "raw_properties"].includes(String(key).toLowerCase())
  }).length
}

function handleFeatureInfo(payload) {
  const feature = payload?.feature ?? payload ?? null
  if (!feature) {
    closeFeaturePanel()
    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 },
    })
    return
  }
  if (currentView.value !== "restoration" || panel?.key !== "restoration-projects") return
  const projectName = row?.projectName || row?.name
  if (!projectName) return
  closeFeaturePanel()
  mapSceneRef.value?.focusProject?.(projectName)
}

function handleDrawingChange(enabled) {
  closeFeaturePanel()
  drawingPointCount.value = 0
  mapSceneRef.value?.setBoundaryDrawing?.(enabled)
}

function handleFinishDrawing() {
  mapSceneRef.value?.finishBoundaryDrawing?.()
}

function handleUndoDrawingPoint() {
  mapSceneRef.value?.undoBoundaryPoint?.()
}

function handleClearAnalysisBoundary() {
  drawingPointCount.value = 0
  mapSceneRef.value?.clearAnalysisBoundary?.()
}

function handleDrawBoundary(payload = {}) {
  drawingPointCount.value = payload.pointCount || payload.points?.length || 0
  if (payload.status === "complete" && payload.feature) {
    drawingPointCount.value = 0
    engineeringAnalysisRef.value?.applyDrawnBoundary?.(payload.feature)
  }
  if (payload.status === "clear") drawingPointCount.value = 0
}

function handleAnalysisResult(payload = {}) {
  mapSceneRef.value?.showAnalysisResult?.({
    boundary: payload.boundary,
    matches: payload.matches,
    preserveView: currentView.value === "engineering-analysis",
  })
  if (currentView.value === "engineering-analysis") return
}

function handleAnalysisProjectFocus(item) {
  const matches = (item?.features || []).filter(Boolean)
  mapSceneRef.value?.showAnalysisResult?.({
    matches,
  })
}

function closeFeaturePanel() {
  selectedFeature.value = null
  featureEmpty.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: 72px;
    left: auto;
    width: 248px;
    height: 430px;

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

    &.is-national-park {
      top: auto;
      left: auto;
      right: 458px;
      bottom: 72px;
      width: 178px;
      height: 118px;
      opacity: 0.92;

      .layer-panel-legend {
        min-height: 0;
      }

      .layer-panel-legend-row {
        height: 28px;
      }

      .m-card-hd-title {
        right: 42px !important;
        font-size: 15px !important;
        letter-spacing: 1px !important;
      }
    }
  }

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

    .topic-switcher {
      width: 650px;
    }

    .topic-switcher-item {
      width: 146px;
    }

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