<template>
  <!-- 降雨时段切换框 -->
  <div v-if="visible" class="rainfall-switcher">
    <div
      v-for="h in hourOptions"
      :key="h"
      class="rainfall-switch-item"
      :class="{ active: currentHours === h, disabled: loading && currentHours !== h }"
      @click="switchHours(h)"
    >
      未来{{ h }}h降雨
    </div>
  </div>

  <!-- 加载中 -->
  <div v-if="loading" class="rainfall-loading">
    <span class="loading-spinner"></span>
    <span>降雨数据加载中...</span>
  </div>

  <!-- 加载失败 -->
  <div v-if="error" class="rainfall-error">
    <span>降雨加载失败：{{ error }}</span>
  </div>

  <!-- 鼠标悬停降雨等级提示 -->
  <div v-if="hoverInfo.visible" class="rainfall-tooltip" :style="{ left: hoverInfo.x + 'px', top: hoverInfo.y + 'px' }">
    {{ hoverInfo.text }}
  </div>
</template>

<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { PROXY_SLT } from '../../config/server_config.js'

const props = defineProps({
  viewer: { type: Object, default: null },
  visible: { type: Boolean, default: true },
  defaultHours: { type: Number, default: 24 }
})

const hourOptions = [24, 48, 72]
const currentHours = ref(props.defaultHours)
const loading = ref(false)
const error = ref('')
const hoverInfo = ref({ visible: false, x: 0, y: 0, text: '' })

let imageryLayer = null
let hitContours = [] // [{ symbol, level, polyLatlng }] 按 symbol 降序，供悬停命中
let isMounted = false
let hoverHandler = null

// 整个降雨图层叠加在底图上的透明度（透出地形）
const LAYER_ALPHA = 0.8

// 降雨等级配置：按 symbol(降雨量阈值 mm) 升序。
// 注意：API 返回的 value 始终为 0，真正的阈值在 symbol 字段。
const RAIN_LEVELS = [
  { min: 0,   name: '小雨',   color: '#A6F0A6', range: '0~10mm' },
  { min: 10,  name: '中雨',   color: '#3AA402', range: '10~25mm' },
  { min: 25,  name: '大雨',   color: '#71B4E8', range: '25~50mm' },
  { min: 50,  name: '暴雨',   color: '#0033FF', range: '50~100mm' },
  { min: 100, name: '大暴雨', color: '#C700CC', range: '100~250mm' },
  { min: 250, name: '特大暴雨', color: '#8B0000', range: '≥250mm' }
]

// 由 symbol 取等级（取 min <= symbol 的最高等级）
const getLevel = (symbol) => {
  let lvl = RAIN_LEVELS[0]
  for (const l of RAIN_LEVELS) {
    if (symbol >= l.min) lvl = l
  }
  return lvl
}

const getViewer = () => props.viewer || window.viewer

// 射线法判断点 [lat, lng] 是否在多边形 [[lat, lng], ...] 内部（用于悬停命中）
const pointInPolygon = (point, polygon) => {
  const [py, px] = point
  let inside = false
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const [yi, xi] = polygon[i]
    const [yj, xj] = polygon[j]
    const intersect = ((yi > py) !== (yj > py)) &&
      (px < (xj - xi) * (py - yi) / (yj - yi) + xi)
    if (intersect) inside = !inside
  }
  return inside
}

// 清除降雨图层
const clearLayer = () => {
  const v = getViewer()
  if (v && imageryLayer) {
    try { v.imageryLayers.remove(imageryLayer) } catch {}
  }
  imageryLayer = null
  hitContours = []
  hoverInfo.value.visible = false
}

// 绘制降雨等值面：把各等级多边形按“小雨先画、高等级后画”的光栅化方式
// 画到一张 canvas 上（2D 画板的画家算法天然保证后画覆盖先画，无 z-fighting、无边界缝），
// 再把这张 canvas 作为单张影像叠加到地形上（imagery 天然贴合地形）。
// 这样彻底避开了 Cesium 地面分类中重叠多边形的 z-fighting 与孔洞边界问题。
const drawContours = (contours) => {
  const v = getViewer()
  if (!v) return

  clearLayer()

  // 按 symbol 分组
  const bySymbol = new Map()
  const presentSymbols = new Set()
  const bounds = { west: Infinity, east: -Infinity, south: Infinity, north: -Infinity }

  for (const c of contours) {
    if (!c.latAndLong || c.latAndLong.length < 3) continue
    const symbol = Number(c.symbol ?? 0)
    if (!bySymbol.has(symbol)) bySymbol.set(symbol, [])
    bySymbol.get(symbol).push(c.latAndLong)
    presentSymbols.add(symbol)
    for (const [lat, lng] of c.latAndLong) {
      if (lng < bounds.west) bounds.west = lng
      if (lng > bounds.east) bounds.east = lng
      if (lat < bounds.south) bounds.south = lat
      if (lat > bounds.north) bounds.north = lat
    }
  }

  if (bounds.west === Infinity) return

  // 光栅化 canvas
  const spanLng = bounds.east - bounds.west
  const spanLat = bounds.north - bounds.south
  const maxDim = 2048
  let cw, ch
  if (spanLng >= spanLat) {
    cw = maxDim
    ch = Math.max(1, Math.round(maxDim * spanLat / spanLng))
  } else {
    ch = maxDim
    cw = Math.max(1, Math.round(maxDim * spanLng / spanLat))
  }

  const canvas = document.createElement('canvas')
  canvas.width = cw
  canvas.height = ch
  const ctx = canvas.getContext('2d')
  ctx.clearRect(0, 0, cw, ch)

  const toX = (lng) => (lng - bounds.west) / spanLng * cw
  const toY = (lat) => (bounds.north - lat) / spanLat * ch

  // 升序绘制：小雨(范围最大)先画在最底，大暴雨最后画在最上层 —— 画家算法保证覆盖关系
  const sortedAsc = [...bySymbol.keys()].sort((a, b) => a - b)
  for (const symbol of sortedAsc) {
    const level = getLevel(symbol)
    ctx.fillStyle = level.color // canvas 内不透明，整体透明度由 imageryLayer.alpha 控制
    for (const poly of bySymbol.get(symbol)) {
      ctx.beginPath()
      for (let i = 0; i < poly.length; i++) {
        const [lat, lng] = poly[i]
        const x = toX(lng)
        const y = toY(lat)
        if (i === 0) ctx.moveTo(x, y)
        else ctx.lineTo(x, y)
      }
      ctx.closePath()
      ctx.fill()
    }
  }

  // 作为单张影像叠加到地形上（天然贴合地形，单张影像无重叠/无缝隙）
  try {
    const dataUrl = canvas.toDataURL('image/png')
    const provider = new Cesium.SingleTileImageryProvider({
      url: dataUrl,
      rectangle: Cesium.Rectangle.fromDegrees(bounds.west, bounds.south, bounds.east, bounds.north)
    })
    imageryLayer = v.imageryLayers.addImageryProvider(provider)
    imageryLayer.alpha = LAYER_ALPHA
  } catch (e) {
    console.error('降雨影像叠加失败:', e)
    ElMessage.error('降雨图层绘制失败')
    return
  }

  // 构建悬停命中数据：按 symbol 降序（高等级优先命中，与画面覆盖关系一致）
  hitContours = []
  for (const symbol of [...presentSymbols].sort((a, b) => b - a)) {
    const level = getLevel(symbol)
    for (const poly of bySymbol.get(symbol)) {
      hitContours.push({ symbol, level, polyLatlng: poly })
    }
  }
}

// 鼠标悬停拾取降雨等级：用地形交点经纬度，对命中数据从高到低做点-多边形检测
const setupHover = () => {
  const v = getViewer()
  if (!v || hoverHandler) return
  hoverHandler = new Cesium.ScreenSpaceEventHandler(v.scene.canvas)
  hoverHandler.setInputAction((movement) => {
    if (!hitContours.length) {
      hoverInfo.value.visible = false
      return
    }
    try {
      const ray = v.camera.getPickRay(movement.endPosition)
      let cartesian = ray ? v.scene.globe.pick(ray, v.scene) : undefined
      // 地形未加载时回退到椭球面取点，保证悬停始终可用
      if (!cartesian) {
        cartesian = v.camera.pickEllipsoid(movement.endPosition, Cesium.Ellipsoid.WGS84)
      }
      if (!cartesian) {
        hoverInfo.value.visible = false
        return
      }
      const carto = Cesium.Cartographic.fromCartesian(cartesian)
      const lng = Cesium.Math.toDegrees(carto.longitude)
      const lat = Cesium.Math.toDegrees(carto.latitude)
      let found = null
      for (const hc of hitContours) {
        if (pointInPolygon([lat, lng], hc.polyLatlng)) { found = hc; break }
      }
      if (found) {
        hoverInfo.value = {
          visible: true,
          x: movement.endPosition.x + 14,
          y: movement.endPosition.y + 14,
          text: found.level.name
        }
      } else {
        hoverInfo.value.visible = false
      }
    } catch {
      hoverInfo.value.visible = false
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}

const destroyHover = () => {
  if (hoverHandler) {
    hoverHandler.destroy()
    hoverHandler = null
  }
  hoverInfo.value.visible = false
}

// 获取降雨数据
const fetchRainfall = async (hours) => {
  const v = getViewer()
  if (!v) {
    ElMessage.error('Cesium viewer 未初始化')
    return
  }

  loading.value = true
  error.value = ''
  clearLayer()

  try {
    const url = `${PROXY_SLT}/Api/LeastRain/${hours}`
    const { data } = await axios.get(url)
    let contours = data?.contours
    if (typeof contours === 'string') {
      contours = JSON.parse(contours)
    }
    if (!Array.isArray(contours) || contours.length === 0) {
      ElMessage.info(`未来${hours}h暂无降雨数据`)
      return
    }
    if (!isMounted) return
    drawContours(contours)
    setupHover()
    ElMessage.success(`已加载未来${hours}h降雨`)
  } catch (e) {
    console.error('获取降雨数据失败:', e)
    error.value = e.message || '请求失败'
    ElMessage.error('获取降雨数据失败')
  } finally {
    loading.value = false
  }
}

// 切换时段
const switchHours = (h) => {
  if (currentHours.value === h || loading.value) return
  currentHours.value = h
  fetchRainfall(h)
}

// 可见性变化
watch(() => props.visible, (val) => {
  if (val) {
    fetchRainfall(currentHours.value)
  } else {
    clearLayer()
    destroyHover()
  }
})

onMounted(() => {
  isMounted = true
  if (props.visible) {
    fetchRainfall(currentHours.value)
  }
})

onUnmounted(() => {
  isMounted = false
  destroyHover()
  clearLayer()
})
</script>

<style scoped>
.rainfall-switcher {
  position: absolute;
  top: 70px;
  left: 20px;
  z-index: 1000;
  display: flex;
  gap: 4px;
  background: rgba(0, 0, 0, 0.55);
  border-radius: 4px;
  padding: 3px;
}
.rainfall-switch-item {
  padding: 4px 10px;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  cursor: pointer;
  border-radius: 3px;
  user-select: none;
  transition: all 0.2s;
  border: 1px solid transparent;
  white-space: nowrap;
}
.rainfall-switch-item:hover {
  background: rgba(255, 255, 255, 0.15);
}
.rainfall-switch-item.active {
  background: #409eff;
  border-color: #409eff;
}
.rainfall-switch-item.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.rainfall-loading,
.rainfall-error {
  position: absolute;
  top: 105px;
  left: 20px;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.loading-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: rain-spin 0.8s linear infinite;
}
@keyframes rain-spin {
  to { transform: rotate(360deg); }
}

/* 鼠标悬停提示 */
.rainfall-tooltip {
  position: absolute;
  z-index: 1001;
  background: rgba(0, 0, 0, 0.78);
  color: #fff;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 13px;
  font-weight: bold;
  pointer-events: none;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}
</style>
