<template>
  <div class="forest-grass-wet">
    <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"
    />

    <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="398"
            :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="398"
            :height="getPanelHeight(panel)"
          >
            <business-panel-renderer :panel="panel" compact column-label-standard />
          </m-card>
        </div>
      </div>

      <div class="fgw-map-layer-tools">
        <layer-panel
          v-model="activeLayerKey"
          v-model:selected-legend-key="selectedLegendKey"
          :topic-key="activeTopicKey"
          :legends="activeLayer?.legends || []"
          :width="248"
          :height="430"
        />
      </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, getTopicConfig } from "@/config/layers"
import { getLayerTownRanking, getTopicBusinessStats } from "@/services/openApi"
import { getGrasslandNdviTimeline } from "@/services/ndviTimeline"
import MapScene from "./map.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 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 resettingTopicLayer = false
let resettingLegend = false

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

const activeTopic = computed(() => getTopicConfig(activeTopicKey.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 activeLayer = computed(() => {
  const layer = baseActiveLayer.value
  if (!layer) return layer
  const 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) return visibleLayer
  return {
    ...visibleLayer,
    layerName: timelineItem.layerName,
    name: `${layer.name} ${timelineItem.label}`,
    timelineDate: timelineItem.label,
    timelineItem,
  }
})
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 = computed(() => 12)
const doublePanelHeight = computed(() => panelHeight.value * 2 + panelGap.value)
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",
  }
})

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
}

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 = ""
  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 图层加载",
  }
  loadBusinessStats(activeTopicKey.value, activeLayerKey.value)
  loadMapRanking(activeTopicKey.value, activeLayerKey.value, null)
  nextTick(() => {
    resettingLegend = false
  })
})

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 requestId = ++businessRequestId
  coreLoading.value = true
  coreStatus.value = "loading"
  const result = await getTopicBusinessStats(topicKey, layerKey)
  if (destroyed || requestId !== businessRequestId) return
  if (topicKey !== activeTopicKey.value) return
  if ((layerKey || "") !== (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) => {
    const value = properties[key]
    if (value === undefined || value === null || value === "") return false
    const normalized = key.toLowerCase()
    if (["geom", "wkt", "geometry", "the_geom", "shape_leng", "shape_length", "length", "perimeter"].includes(normalized)) return false
    if (["gid", "id", "objectid", "object_id", "fid", "key_uid", "bsm", "ysdm"].includes(normalized)) return false
    return true
  })
  const hasGeometry = Boolean(feature?.feature?.geometry || feature?.geometry)
  return keys.length + (hasGeometry ? 1 : 0)
}

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
}

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

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

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 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(".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: 700px;
    perspective-origin: 50% 50%;
    pointer-events: all;
  }

  .fgw-left {
    left: 32px;
  }

  .fgw-right {
    right: 32px;
  }

  .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: 40px;
      bottom: 10px;
      color: #f3feff;
    }

    .m-card-bd-bg {
      filter: brightness(1.08) saturate(1.04);
    }

    .m-card-bd-bg::after {
      content: "";
      position: absolute;
      inset: 4px 5px 6px;
      z-index: 0;
      pointer-events: none;
      border: 1px solid rgba(48, 220, 255, 0.14);
      background:
        linear-gradient(180deg, rgba(17, 73, 92, 0.16), rgba(6, 29, 44, 0.14)),
        linear-gradient(90deg, rgba(48, 220, 255, 0.1), transparent 42%, rgba(48, 220, 255, 0.06));
      box-shadow:
        inset 0 0 0 1px rgba(48, 220, 255, 0.06),
        inset 0 12px 28px rgba(48, 220, 255, 0.04);
    }
  }

  .m-card-hd-title {
    color: #ffffff !important;
    background: none !important;
    -webkit-text-fill-color: #ffffff !important;
    font-weight: 800 !important;
    filter: brightness(1.2) contrast(1.08) !important;
    text-shadow:
      0 0 1px rgba(255, 255, 255, 0.96),
      0 0 8px rgba(255, 255, 255, 0.22) !important;
  }

  .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>
