<template>
  <div class="relative bg-white py-4 dark:bg-gray-950">
    <div class="text-center">
      <h2 class="text-title-sm font-semibold text-gray-950 dark:text-white lg:text-title-md">全国IP分布热点图</h2>
      <p v-if="currentId !== initialMapId" class="mt-3 text-sm text-sky-600 dark:text-sky-300">{{ currentName }}</p>
    </div>

    <div class="mt-10 grid items-center gap-8 lg:grid-cols-[290px_1fr_220px]">
      <div class="lg:pt-4">
        <h3 class="text-xl font-semibold text-gray-700 dark:text-gray-200">节点遍布全国150+城市</h3>
        <ul class="mt-6 grid gap-3 text-base leading-7 text-gray-600 dark:text-gray-300">
          <li v-for="item in summaryItems" :key="item" class="flex gap-2">
            <span class="mt-3 h-1.5 w-1.5 shrink-0 rounded-full bg-gray-500 dark:bg-gray-400"></span>
            <span>{{ item }}</span>
          </li>
        </ul>
      </div>

      <div>
        <div class="relative min-h-[420px]">
          <svg class="h-full min-h-[420px] w-full drop-shadow-[0_20px_36px_rgba(14,165,233,0.16)]" :viewBox="`0 0 ${svgWidth} ${svgHeight}`" role="img" aria-label="IP 节点热力地图">
            <path
              v-for="region in regions"
              :key="region.key"
              :d="region.path"
              :fill="region.fill"
              class="cursor-pointer stroke-white stroke-[1.3] transition hover:opacity-80 dark:stroke-gray-950"
              @click="drillDown(region)"
            />
            <text
              v-for="region in labelRegions"
              :key="`${region.key}-label`"
              :x="region.labelX"
              :y="region.labelY"
              class="pointer-events-none select-none fill-gray-700 text-[11px] font-semibold dark:fill-gray-200"
              text-anchor="middle"
            >
              {{ region.name }}
            </text>
          </svg>

          <div v-if="loading" class="absolute inset-0 grid place-items-center bg-white/70 text-sm font-semibold text-sky-700 backdrop-blur dark:bg-gray-950/70 dark:text-sky-200">
            正在加载地图...
          </div>
          <div v-if="error" class="absolute inset-x-4 bottom-4 rounded-xl bg-error-50 px-4 py-3 text-sm text-error-600 dark:bg-error-500/15 dark:text-error-300">
            {{ error }}
          </div>
        </div>

        <div class="mt-6 flex flex-wrap items-center justify-center gap-5 text-sm text-gray-700 dark:text-gray-300">
          <span class="inline-flex items-center gap-2">
            <i class="h-4 w-6 rounded bg-[#1f7df2]"></i>
            已开通
          </span>
          <span class="inline-flex items-center gap-2">
            <i class="h-4 w-6 rounded bg-[#d8edff]"></i>
            暂未开通
          </span>
        </div>
      </div>

      <div class="flex h-full flex-col justify-end gap-4 pb-12">
        <strong class="text-xl font-semibold text-gray-600 dark:text-gray-300">全国IP分布情况</strong>
        <span class="text-sm text-gray-400 dark:text-gray-500">点击地图区域可进入下一级</span>
        <button
          v-if="currentId !== initialMapId"
          type="button"
          class="h-10 w-fit rounded-lg border border-sky-200 bg-white px-4 text-sm font-semibold text-sky-700 transition hover:bg-sky-50 dark:border-sky-500/20 dark:bg-white/[0.06] dark:text-sky-200"
          @click="resetMap"
        >
          返回全国
        </button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'

type Position = [number, number]
type Ring = Position[]
type PolygonCoordinates = Ring[]
type MultiPolygonCoordinates = PolygonCoordinates[]

type GeoFeature = {
  type: 'Feature'
  properties?: {
    name?: string
    adcode?: number | string
    center?: Position
    centroid?: Position
    childrenNum?: number
  }
  geometry?: {
    type: 'Polygon' | 'MultiPolygon'
    coordinates: PolygonCoordinates | MultiPolygonCoordinates
  }
}

type GeoJson = {
  type: 'FeatureCollection'
  features: GeoFeature[]
}

type RegionPath = {
  key: string
  name: string
  adcode: string
  path: string
  fill: string
  labelX: number
  labelY: number
}

const initialMapId = '100000_full'
const svgWidth = 760
const svgHeight = 500
const padding = 24
const openedColor = '#1f7df2'
const unopenedColor = '#d8edff'
const openedProvinceCodes = new Set([
  '110000',
  '120000',
  '130000',
  '140000',
  '210000',
  '220000',
  '230000',
  '310000',
  '320000',
  '330000',
  '340000',
  '350000',
  '360000',
  '370000',
  '410000',
  '420000',
  '430000',
  '440000',
  '450000',
  '460000',
  '500000',
  '510000',
  '520000',
  '530000',
])
const summaryItems = ['日更IP量超300万+', '可用率95%以上', '支持HTTP/HTTPS']

const currentId = ref(initialMapId)
const currentName = ref('全国 IP 节点热力图')
const geoJson = ref<GeoJson | null>(null)
const loading = ref(false)
const error = ref('')

const regions = computed<RegionPath[]>(() => {
  if (!geoJson.value?.features?.length) return []
  return buildRegionPaths(geoJson.value.features)
})

const labelRegions = computed(() => {
  if (currentId.value === initialMapId) return []
  return regions.value.length <= 45 ? regions.value : regions.value.slice(0, 45)
})

onMounted(() => {
  void loadMap(initialMapId, '全国 IP 节点热力图', false)
})

async function drillDown(region: RegionPath) {
  if (!region.adcode) {
    await resetMap()
    return
  }
  await loadMap(`${region.adcode}_full`, `${region.name} IP 节点热力图`, true)
}

async function resetMap() {
  await loadMap(initialMapId, '全国 IP 节点热力图', false)
}

async function loadMap(id: string, name: string, fallbackToRoot: boolean) {
  loading.value = true
  error.value = ''
  try {
    const response = await fetch(mapUrl(id))
    if (!response.ok) throw new Error(`地图数据请求失败：${response.status}`)
    const payload = await response.json()
    const data = normalizeGeoJson(payload)
    if (!data?.features?.length) throw new Error('暂无下级地图数据')
    geoJson.value = data
    currentId.value = id
    currentName.value = name
  } catch (err) {
    if (fallbackToRoot) {
      await loadMap(initialMapId, '全国 IP 节点热力图', false)
      error.value = '当前区域暂无下级地图，已返回全国。'
      return
    }
    error.value = err instanceof Error ? err.message : '地图数据加载失败'
  } finally {
    loading.value = false
  }
}

function mapUrl(id: string) {
  const normalizedId = id.endsWith('_full') ? id : `${id}_full`
  const base = window.location.hostname.endsWith('qiyunip.com') ? '' : 'https://www.qiyunip.com'
  return `${base}/api/map/getjson?id=${encodeURIComponent(normalizedId)}`
}

function normalizeGeoJson(payload: unknown): GeoJson | null {
  const value = payload as { type?: string; features?: GeoFeature[]; data?: unknown }
  if (value?.type === 'FeatureCollection' && Array.isArray(value.features)) return value as GeoJson
  const data = value?.data as { type?: string; features?: GeoFeature[] } | undefined
  if (data?.type === 'FeatureCollection' && Array.isArray(data.features)) return data as GeoJson
  return null
}

function buildRegionPaths(features: GeoFeature[]) {
  const bbox = getFeatureBounds(features)
  const scale = Math.min(
    (svgWidth - padding * 2) / Math.max(bbox.maxX - bbox.minX, 1),
    (svgHeight - padding * 2) / Math.max(bbox.maxY - bbox.minY, 1),
  )
  const offsetX = (svgWidth - (bbox.maxX - bbox.minX) * scale) / 2
  const offsetY = (svgHeight - (bbox.maxY - bbox.minY) * scale) / 2
  const project = ([lng, lat]: Position) => ({
    x: offsetX + (lng - bbox.minX) * scale,
    y: offsetY + (bbox.maxY - lat) * scale,
  })

  return features
    .map((feature, index) => {
      const name = feature.properties?.name || '未知区域'
      const adcode = feature.properties?.adcode ? String(feature.properties.adcode) : ''
      const center = feature.properties?.centroid || feature.properties?.center || getFeatureCenter(feature)
      const label = project(center)
      return {
        key: `${adcode || name}-${index}`,
        name,
        adcode,
        path: geometryToPath(feature, project),
        fill: isOpenedRegion(adcode, index) ? openedColor : unopenedColor,
        labelX: label.x,
        labelY: label.y,
      }
    })
    .filter((item) => item.path)
}

function geometryToPath(feature: GeoFeature, project: (position: Position) => { x: number; y: number }) {
  const geometry = feature.geometry
  if (!geometry) return ''
  if (geometry.type === 'Polygon') return polygonToPath(geometry.coordinates as PolygonCoordinates, project)
  if (geometry.type === 'MultiPolygon') {
    return (geometry.coordinates as MultiPolygonCoordinates).map((polygon) => polygonToPath(polygon, project)).join(' ')
  }
  return ''
}

function polygonToPath(polygon: PolygonCoordinates, project: (position: Position) => { x: number; y: number }) {
  return polygon
    .map((ring) =>
      ring
        .map((position, index) => {
          const point = project(position)
          return `${index === 0 ? 'M' : 'L'}${point.x.toFixed(2)},${point.y.toFixed(2)}`
        })
        .join(' ') + ' Z',
    )
    .join(' ')
}

function getFeatureBounds(features: GeoFeature[]) {
  const positions = features.flatMap((feature) => getFeaturePositions(feature))
  return positions.reduce(
    (bbox, [lng, lat]) => ({
      minX: Math.min(bbox.minX, lng),
      minY: Math.min(bbox.minY, lat),
      maxX: Math.max(bbox.maxX, lng),
      maxY: Math.max(bbox.maxY, lat),
    }),
    { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
  )
}

function getFeaturePositions(feature: GeoFeature) {
  const geometry = feature.geometry
  if (!geometry) return []
  if (geometry.type === 'Polygon') return (geometry.coordinates as PolygonCoordinates).flat()
  return (geometry.coordinates as MultiPolygonCoordinates).flat(2)
}

function getFeatureCenter(feature: GeoFeature): Position {
  const positions = getFeaturePositions(feature)
  if (!positions.length) return [105, 35]
  const sum = positions.reduce((acc, [lng, lat]) => ({ lng: acc.lng + lng, lat: acc.lat + lat }), { lng: 0, lat: 0 })
  return [sum.lng / positions.length, sum.lat / positions.length]
}

function isOpenedRegion(adcode: string, index: number) {
  if (!adcode) return false
  if (currentId.value === initialMapId) return openedProvinceCodes.has(adcode)
  return index % 3 !== 0
}
</script>
