<template>
  <div class="app-container">
    <el-card shadow="never">
      <el-form :inline="true" :model="queryForm" size="small">
        <el-form-item label="时间范围">
          <el-date-picker v-model="timeRange" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss"
            range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" style="width: 380px" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="Search" :loading="loadingMap.sankey" @click="handleQuery">查询</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 桑基图 -->
    <el-card v-loading="loadingMap.sankey" shadow="never" class="mt-4">
      <template #header>
        <div class="card-header">
          <span class="font-bold">温区流转桑基图</span>
          <HelpButton title="桑基图（Sankey Diagram）" :content="ADVANCED_HELP" />
        </div>
      </template>
      <div ref="sankeyChartRef" class="chart-container-lg" />
    </el-card>

    <!-- 主题河流图 -->
    <el-card v-loading="loadingMap.river" shadow="never" class="mt-4">
      <template #header>
        <div class="card-header">
          <span class="font-bold">温度主题河流图</span>
          <HelpButton title="主题河流图（ThemeRiver）" :content="ADVANCED_HELP" />
        </div>
      </template>
      <div ref="themeRiverChartRef" class="chart-container-lg" />
    </el-card>

    <!-- 矩形树图 + 旭日图 -->
    <el-row :gutter="16" class="mt-4">
      <el-col :xs="24" :sm="12">
        <el-card v-loading="loadingMap.treemap" shadow="never">
          <template #header>
            <div class="card-header">
              <span class="font-bold">温度矩形树图（按测点平均温度）</span>
              <HelpButton title="矩形树图（Treemap）" :content="ADVANCED_HELP" />
            </div>
          </template>
          <div ref="treemapChartRef" class="chart-container-lg" />
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="12">
        <el-card v-loading="loadingMap.sunburst" shadow="never">
          <template #header>
            <div class="card-header">
              <span class="font-bold">温度旭日图（温区→测点）</span>
              <HelpButton title="旭日图（Sunburst）" :content="ADVANCED_HELP" />
            </div>
          </template>
          <div ref="sunburstChartRef" class="chart-container-lg" />
        </el-card>
      </el-col>
    </el-row>

    <!-- 平行坐标图 -->
    <el-card v-loading="loadingMap.parallel" shadow="never" class="mt-4">
      <template #header>
        <div class="card-header">
          <span class="font-bold">测点多维温度画像（平行坐标图）</span>
          <HelpButton title="平行坐标图（Parallel Coordinates）" :content="ADVANCED_HELP" />
        </div>
      </template>
      <div ref="parallelChartRef" class="chart-container-lg" />
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, shallowRef, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue'
import * as echarts from 'echarts'
import { getSankeyData, getThemeRiverData, getTreemapData, getSunburstData, getParallelData } from '@/api/ems/report/tempBoard'
import type { TempBoardQuery, TempBoardAdvancedVO } from '@/api/ems/report/tempBoard'
import HelpButton from '../components/HelpButton.vue'
import { ADVANCED_HELP } from '../components/helpContent'
import { useChartResize } from '../components/useChartResize'

defineOptions({ name: 'TempBoardAdvanced' })

const timeRange = ref<[string, string]>(['', ''])
const queryForm = reactive<TempBoardQuery>({})

// 每图独立 loading，替代单一 loading
const loadingMap = reactive({
  sankey: false,
  river: false,
  treemap: false,
  sunburst: false,
  parallel: false
})

// 图表数据使用 shallowRef 避免深层响应式代理
const sankeyData = shallowRef<TempBoardAdvancedVO[]>([])
const riverData = shallowRef<TempBoardAdvancedVO[]>([])
const treemapData = shallowRef<TempBoardAdvancedVO[]>([])
const sunburstData = shallowRef<TempBoardAdvancedVO[]>([])
const parallelData = shallowRef<TempBoardAdvancedVO[]>([])

// 图表容器 DOM 引用
const sankeyChartRef = ref<HTMLElement>()
const themeRiverChartRef = ref<HTMLElement>()
const treemapChartRef = ref<HTMLElement>()
const sunburstChartRef = ref<HTMLElement>()
const parallelChartRef = ref<HTMLElement>()

const { getChart, disposeAll } = useChartResize(sankeyChartRef, themeRiverChartRef, treemapChartRef, sunburstChartRef, parallelChartRef)

// ==================== 请求取消控制 ====================

/** 每次查询生成唯一 ID，旧请求结果自动丢弃 */
let currentQueryId = 0
let abortController: AbortController | null = null

function cancelPending() {
  if (abortController) {
    abortController.abort()
  }
  abortController = new AbortController()
}

// ==================== 时间初始化 ====================

/** 初始化默认时间范围：昨天8:30 ~ 今天8:30 */
function initTimeRange() {
  const now = new Date()
  const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 30, 0)
  const start = new Date(end.getTime() - 24 * 3600 * 1000)
  const p = (n: number) => n.toString().padStart(2, '0')
  const fmt = (d: Date) => `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
  timeRange.value = [fmt(start), fmt(end)]
}

// ==================== 懒加载控制 ====================

/** 记录已触发的图表，IntersectionObserver 只触发一次 */
const loadedCards = reactive(new Set<string>())

/** 通用懒加载：进入视口后发起请求并渲染 */
function lazyLoadChart(
  cardKey: string,
  elRef: { value: HTMLElement | undefined },
  fetchFn: (params: TempBoardQuery) => Promise<any>,
  renderFn: (data: TempBoardAdvancedVO[]) => void,
  loadingKey: keyof typeof loadingMap
) {
  if (loadedCards.has(cardKey) || !elRef.value) return
  // 标记已触发，避免重复
  loadedCards.add(cardKey)

  const queryId = currentQueryId
  loadingMap[loadingKey] = true

  fetchFn(queryForm).then(res => {
    // 只处理最新请求的结果
    if (queryId === currentQueryId) {
      nextTick(() => renderFn(res.data))
    }
  }).catch(err => {
    if (err?.name !== 'CanceledError' && err?.name !== 'AbortError') {
      console.error(`[${cardKey}] 加载失败:`, err)
    }
  }).finally(() => {
    if (queryId === currentQueryId) {
      loadingMap[loadingKey] = false
    }
  })
}

/** 为每个图表容器注册 IntersectionObserver */
const observerMap = new Map<HTMLElement, IntersectionObserver>()

function registerObserver(
  cardKey: string,
  elRef: { value: HTMLElement | undefined },
  fetchFn: (params: TempBoardQuery) => Promise<any>,
  renderFn: (data: TempBoardAdvancedVO[]) => void,
  loadingKey: keyof typeof loadingMap
) {
  // 等待 DOM 渲染后注册
  nextTick(() => {
    const el = elRef.value
    if (!el || observerMap.has(el)) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          lazyLoadChart(cardKey, elRef, fetchFn, renderFn, loadingKey)
          observer.disconnect()
          observerMap.delete(el)
        }
      },
      { threshold: 0.05 }
    )
    observer.observe(el)
    observerMap.set(el, observer)
  })
}

// ==================== 查询入口 ====================

async function handleQuery() {
  if (timeRange.value?.[0]) {
    queryForm.startTime = timeRange.value[0]
    queryForm.endTime = timeRange.value[1]
  }
  // 取消上一次未完成的请求
  cancelPending()
  currentQueryId++

  // 清空旧数据和观察器
  loadedCards.clear()
  observerMap.forEach(obs => obs.disconnect())
  observerMap.clear()

  // 注册懒加载观察器
  registerObserver('sankey', sankeyChartRef, getSankeyData, renderSankey, 'sankey')
  registerObserver('river', themeRiverChartRef, getThemeRiverData, renderThemeRiver, 'river')
  registerObserver('treemap', treemapChartRef, getTreemapData, renderTreemap, 'treemap')
  registerObserver('sunburst', sunburstChartRef, getSunburstData, renderSunburst, 'sunburst')
  registerObserver('parallel', parallelChartRef, getParallelData, renderParallel, 'parallel')
}

// ==================== 图表渲染函数 ====================

/** 桑基图 */
function renderSankey(data: TempBoardAdvancedVO[]) {
  const chart = getChart(sankeyChartRef)
  if (!chart || !data.length) return
  const nodes = new Set<string>()
  data.forEach(d => { nodes.add(d.fromNode!); nodes.add(d.toNode!) })
  chart.setOption({
    animation: false,
    tooltip: {
      trigger: 'item',
      formatter: (p: any) => p.dataType === 'edge'
        ? `${p.data.source} → ${p.data.target}<br/>设备数: <b>${p.data.value}</b>`
        : `${p.name}`
    },
    series: [{
      type: 'sankey',
      layout: 'none',
      emphasis: { focus: 'adjacency' },
      data: [...nodes].map(n => ({ name: n })),
      links: data.map(d => ({ source: d.fromNode, target: d.toNode, value: d.flowCount })),
      lineStyle: { color: 'gradient', curveness: 0.5 },
      label: { color: '#333', fontSize: 12 },
      itemStyle: { borderWidth: 0 }
    }]
  }, { notMerge: true, lazyUpdate: true })
}

/** 主题河流图 - 开启降采样 */
function renderThemeRiver(data: TempBoardAdvancedVO[]) {
  const chart = getChart(themeRiverChartRef)
  if (!chart || !data.length) return
  chart.setOption({
    animation: false,
    tooltip: {
      trigger: 'axis',
      formatter: (params: any) => {
        const p = params[0]
        return `${p.data[2]}<br/>时间: ${p.data[0]}<br/>温度: <b>${p.data[1]}℃</b>`
      }
    },
    legend: { top: 0, textStyle: { color: '#666' } },
    singleAxis: { type: 'time', axisLabel: { color: '#666' } },
    series: [{
      type: 'themeRiver',
      sampling: 'lttb',
      data: data.map(d => [d.statTime, d.avgTemp, d.monitorName || d.monitorId]),
      emphasis: { itemStyle: { shadowBlur: 20, shadowColor: 'rgba(0,0,0,0.8)' } },
      label: { show: false }
    }]
  }, { notMerge: true, lazyUpdate: true })
}

/** 矩形树图 - tooltip 使用 Map 替代 O(n) find */
function renderTreemap(data: TempBoardAdvancedVO[]) {
  const chart = getChart(treemapChartRef)
  if (!chart || !data.length) return

  // 预建 Map，避免 tooltip 每次 hover 做 O(n) 线性扫描
  const itemMap = new Map(data.map(d => [d.monitorName || d.monitorId, d]))

  chart.setOption({
    animation: false,
    tooltip: {
      formatter: (p: any) => {
        const item = itemMap.get(p.name)
        return `${p.name}<br/>平均温度: <b>${p.value?.toFixed(2)}℃</b>${item ? `<br/>样本数: ${item.sampleCount}` : ''}`
      }
    },
    series: [{
      type: 'treemap',
      data: data.map(d => ({
        name: d.monitorName || d.monitorId,
        value: d.avgTemp,
        itemStyle: {
          color: d.avgTemp >= 30 ? '#f56c6c' : d.avgTemp >= 20 ? '#e6a23c' : d.avgTemp >= 10 ? '#67c23a' : '#409eff'
        }
      })),
      leafDepth: 1,
      roam: false,
      label: { show: true, formatter: '{b}\n{c}℃', fontSize: 12, color: '#fff' },
      upperLabel: { show: true, height: 24, color: '#fff' },
      itemStyle: { borderColor: '#fff', borderWidth: 2, gapWidth: 2 },
      levels: [
        { itemStyle: { borderColor: '#333', borderWidth: 2, gapWidth: 2 } },
        { itemStyle: { borderColor: '#fff', borderWidth: 1, gapWidth: 1 } }
      ]
    }]
  }, { notMerge: true, lazyUpdate: true })
}

/** 旭日图 - 同一温区内同名测点合并 sampleCount */
function renderSunburst(data: TempBoardAdvancedVO[]) {
  const chart = getChart(sunburstChartRef)
  if (!chart || !data.length) return

  const colorMap: Record<string, string> = {
    '低温': '#409eff', '偏低': '#67c23a', '正常': '#95de64',
    '偏高': '#e6a23c', '高温': '#f56c6c'
  }
  // key = tempBucket, value = { name, children: Map<monitorName, aggregated> }
  const bucketMap = new Map<string, { name: string; value: number; children: any[] }>()

  data.forEach(d => {
    if (!bucketMap.has(d.tempBucket!)) {
      bucketMap.set(d.tempBucket!, { name: d.tempBucket!, value: 0, children: [] })
    }
    const bucket = bucketMap.get(d.tempBucket!)!
    const mName = d.monitorName || d.monitorId

    // 在 children 中查找是否已有同名测点，有则累加 sampleCount
    const existing = bucket.children.find(c => c.name === mName)
    if (existing) {
      existing.value += d.sampleCount ?? 0
    } else {
      bucket.children.push({
        name: mName,
        value: d.sampleCount ?? 0,
        itemStyle: { color: colorMap[d.tempBucket!] || undefined }
      })
    }
    bucket.value += d.sampleCount ?? 0
  })

  chart.setOption({
    animation: false,
    tooltip: { formatter: '{b}: {c}样本' },
    series: [{
      type: 'sunburst',
      data: [...bucketMap.values()].map(b => ({
        ...b,
        itemStyle: { color: colorMap[b.name] || undefined }
      })),
      radius: ['15%', '90%'],
      label: { rotate: 'radial', fontSize: 11, color: '#333' },
      itemStyle: { borderWidth: 2, borderColor: '#fff' },
      emphasis: { focus: 'ancestor' }
    }]
  }, { notMerge: true, lazyUpdate: true })
}

/** 平行坐标图 - 恢复 per-monitor series + scroll legend，保留 IQR 轴范围 + 关闭动画 */
function renderParallel(data: TempBoardAdvancedVO[]) {
  const chart = getChart(parallelChartRef)
  if (!chart || !data.length) return

  const dimNames = ['测点', '平均温度', '最高温度', '最低温度', '标准差', '平均延迟(s)']
  const fields = ['avgTemp', 'maxTemp', 'minTemp', 'tempStddev', 'avgDelay'] as const
  const colors = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#9b59b6', '#1abc9c', '#e74c3c', '#2ecc71']

  // 限制最多展示 Top 100 测点，避免浏览器崩溃
  const limited = data.slice(0, 100)

  // 基于 IQR 计算轴范围
  function calcAxisRange(field: string): { min: number; max: number } {
    const values = limited.map(d => (d as any)[field] as number).filter(v => v != null).sort((a, b) => a - b)
    if (values.length === 0) return { min: 0, max: 100 }
    if (values.length <= 2) {
      const range = values[values.length - 1] - values[0]
      const pad = Math.max(range * 0.1, 1)
      return { min: Math.floor(values[0] - pad), max: Math.ceil(values[values.length - 1] + pad) }
    }
    const q1 = values[Math.floor(values.length * 0.25)]
    const q3 = values[Math.floor(values.length * 0.75)]
    const iqr = q3 - q1
    const lower = Math.min(q1 - 1.5 * iqr, values[0])
    const upper = Math.max(q3 + 1.5 * iqr, values[values.length - 1])
    const pad = Math.max((upper - lower) * 0.05, 0.5)
    return { min: Math.floor(lower - pad), max: Math.ceil(upper + pad) }
  }

  const axisRanges = fields.map(f => calcAxisRange(f))

  chart.setOption({
    animation: false,
    tooltip: { trigger: 'item' },
    legend: {
      data: limited.map(d => d.monitorName || d.monitorId),
      top: 0,
      type: 'scroll',
      textStyle: { color: '#666', fontSize: 11 }
    },
    parallelAxis: [
      {
        type: 'category',
        data: limited.map(d => d.monitorName || d.monitorId),
        dim: 0,
        name: dimNames[0],
        axisLabel: { rotate: 30, color: '#666', fontSize: 10 }
      },
      ...fields.map((f, i) => ({
        type: 'value' as const,
        dim: i + 1,
        name: dimNames[i + 1],
        min: axisRanges[i].min,
        max: axisRanges[i].max,
        nameLocation: 'end' as const,
        nameTextStyle: { color: '#333', fontSize: 12 },
        axisLabel: { color: '#666', fontSize: 11 },
        splitLine: { show: true, lineStyle: { color: '#eee' } },
        axisTick: { show: true }
      }))
    ],
    parallel: { left: 100, right: 50, top: 50, bottom: 30 },
    series: limited.map((d, idx) => ({
      name: d.monitorName || d.monitorId,
      type: 'parallel',
      data: [[d.monitorName || d.monitorId, d.avgTemp, d.maxTemp, d.minTemp, d.tempStddev, d.avgDelay]],
      lineStyle: { width: 2, opacity: 0.7, color: colors[idx % colors.length] },
      smooth: true
    }))
  }, { notMerge: true, lazyUpdate: true })
}

// ==================== 生命周期 ====================

onMounted(() => {
  initTimeRange()
  handleQuery()
})

onBeforeUnmount(() => {
  // 清理所有观察器和图表
  observerMap.forEach(obs => obs.disconnect())
  observerMap.clear()
  cancelPending()
  disposeAll()
})
</script>

<style scoped>
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.chart-container-lg {
  width: 100%;
  min-height: 400px;
}
</style>
