<template>
  <div class="boxplot-container">
    <div class="chart-container">
      <div class="header">
        <div class="chart-title">大气重金属浓度统计箱线图</div>
        <p>展示各重金属浓度的分布特征（最小值、四分位数、中位数、最大值）</p>
        <p class="sample-subtitle">样本来源：{{ totalPoints }}个数据</p>
      </div>

      <div v-if="isLoading" class="loading-state">
        <span class="spinner"></span> 数据加载中...
      </div>

      <div v-else-if="error" class="error-state">
        ❌ 加载失败：{{ error.message }}
      </div>

      <div v-else>
        <div class="chart-wrapper">
          <v-chart :option="chartOption" autoresize />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import * as echarts from 'echarts'
import VChart from 'vue-echarts'
import axios from 'axios'
import { ref, onMounted ,computed} from 'vue'

export default {
  components: { VChart },
  setup() {
    // -------- 核心配置 --------
    const apiUrl = ref('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data')
    const heavyMetals = [
      { key: 'Cr_particulate', name: '铬 (Cr)', color: '#FF9800' },
      { key: 'As_particulate', name: '砷 (As)', color: '#4CAF50' },
      { key: 'Cd_particulate', name: '镉 (Cd)', color: '#9C27B0' },
      { key: 'Hg_particulate', name: '汞 (Hg)', color: '#2196F3' },
      { key: 'Pb_particulate', name: '铅 (Pb)', color: '#F44336' },
     // { key: 'particle_weight', name:'大气污染物重量' }
    ]

    // -------- 状态 --------
    const sampleData = ref([])    // 存储 properties 数据
    const chartOption = ref({})   // ECharts 配置
    const isLoading = ref(true)   // 加载状态
    const error = ref(null)       // 错误信息
    const statsByIndex = ref([])  // 缓存统计结果（与 x 轴对齐）
    const totalPoints = computed(() => sampleData.value.length)


    // -------- 工具函数 --------
    /** 日志工具（带颜色区分） */
    const log = (message, metalName = '') => {
      console.log(
        `%c[${metalName || '全局'}] %c${message}`,
        'color:#4CAF50;font-weight:bold',
        'color:#333'
      )
    }

    /** 计算百分位数（线性插值法） */
    const calculatePercentile = (sortedArray, percentile) => {
      const n = sortedArray.length
      if (n === 0) return null
      if (percentile <= 0) return sortedArray[0]
      if (percentile >= 100) return sortedArray[n - 1]
      const index = (n - 1) * (percentile / 100)
      const lowerIndex = Math.floor(index)
      const upperIndex = lowerIndex + 1
      const fraction = index - lowerIndex
      if (upperIndex >= n) return sortedArray[lowerIndex]
      return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex])
    }

    // -------- 数据统计 --------
    /** 计算每个重金属的箱线图统计量（min/q1/median/q3/max） */
    const calculateBoxplotStats = () => {
      const stats = []
      heavyMetals.forEach((metal) => {
        log(`开始处理 ${metal.name}`, metal.name)
        // 1. 提取原始值
        const rawValues = sampleData.value.map(item => item[metal.key])
        log(`原始值：[${rawValues.slice(0, 5)}${rawValues.length > 5 ? ', ...' : ''}]`, metal.name)

        // 2. 过滤无效值（NaN、非数字）
        const values = rawValues
          .map((val, idx) => {
            const num = Number(val)
            if (isNaN(num)) {
              log(`⚠️ 第${idx+1}条数据无效: ${val}`, metal.name)
              return null
            }
            return num
          })
          .filter(v => v !== null)
        log(`有效数据量：${values.length} 条`, metal.name)

        // 3. 无有效数据时，记录空统计
        if (values.length === 0) {
          stats.push({ ...metal, min: null, q1: null, median: null, q3: null, max: null })
          return
        }

        // 4. 排序并计算统计量
        const sorted = [...values].sort((a, b) => a - b)
        const min = sorted[0]
        const max = sorted[sorted.length - 1]
        const q1 = calculatePercentile(sorted, 25)
        const median = calculatePercentile(sorted, 50)
        const q3 = calculatePercentile(sorted, 75)

        log(`统计结果：min=${min}, q1=${q1}, median=${median}, q3=${q3}, max=${max}`, metal.name)
        stats.push({ ...metal, min, q1, median, q3, max })
      })
      return stats
    }

    /** 构建 ECharts 箱线图数据 */
    const buildBoxplotData = (stats) => {
      const xAxisData = heavyMetals.map(m => m.name)
      // 与 x 轴顺序对齐（确保 tooltip 能正确匹配）
      statsByIndex.value = heavyMetals.map(m => 
        stats.find(s => s.key === m.key) || { ...m, min: null, q1: null, median: null, q3: null, max: null }
      )
      // 生成箱线图数据：[min, q1, median, q3, max]
      const data = statsByIndex.value.map(s => 
        s.min === null ? [null, null, null, null, null] : [s.min, s.q1, s.median, s.q3, s.max]
      )
      return { xAxisData, data }
    }

    // -------- 图表初始化 --------
    const initChart = () => {
      log('开始初始化图表')
      const stats = calculateBoxplotStats()
      const { xAxisData, data } = buildBoxplotData(stats)

      // ECharts 配置（重点检查 series 数据格式）
      chartOption.value = {
        tooltip: {
          trigger: 'item',
          formatter: (params) => {
            const s = statsByIndex.value[params.dataIndex]
            if (!s || s.min === null) {
              return `<div style="font-weight:bold;color:#f56c6c">${xAxisData[params.dataIndex]}</div><div>无有效数据</div>`
            }
            
            return `<div style="font-weight:bold">${xAxisData[params.dataIndex]}</div>
              <div style="margin-top:8px">
                <div>最小值：<span style="color:#5a5;">${s.min.toFixed(4)}</span></div>
                <div>下四分位：<span style="color:#d87a80;">${s.q1.toFixed(4)}</span></div>
                <div>中位数：<span style="color:#f56c6c;font-weight:bold;">${s.median.toFixed(4)}</span></div>
                <div>上四分位：<span style="color:#d87a80;">${s.q3.toFixed(4)}</span></div>
                <div>最大值：<span style="color:#5a5;">${s.max.toFixed(4)}</span></div>
              </div>`
          }
        },
        xAxis: {
          type: 'category',
          data: xAxisData,
          name: '重金属类型',
          nameLocation: 'middle',
          nameGap: 45,
          axisLabel: { color: '#555', rotate: 30, fontWeight: 'bold',fontSize:11 }
        },
        yAxis: {
          type: 'value',
          name: 'mg/kg',
          nameLocation: 'end',
          nameGap: 5,
          axisLabel: { color: '#555', fontWeight: 'bold' ,fontSize:11},
          splitLine: { lineStyle: { color: '#f0f0f0' } }
        },
        series: [{
          name: '重金属浓度分布',
          type: 'boxplot',
          data, // 必须是 [[min,q1,median,q3,max], ...] 格式
          itemStyle: {
            color: (p) => (heavyMetals[p.dataIndex]?.color || '#1890ff'),
            borderWidth: 2
          },
          emphasis: {
            itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)', borderWidth: 3 }
          }
        }],
        grid: { top: '8%', right: '5%', left: '12%', bottom: '20%' }
      }
      isLoading.value = false
      log('图表初始化完成')
    }

    // -------- 接口请求 --------
   onMounted(async () => {
  try {
    log('发起API请求...')
    const response = await axios.get(apiUrl.value)
    console.log('接口原始响应：', response.data) // 调试必看！

    let data = response.data

    // ✅ 兼容接口返回字符串的情况（比如后端没设置 application/json）
    if (typeof data === 'string') {
      try {
        // 兼容 NaN（非标准 JSON 值）→ 替换为 null
        data = JSON.parse(data.replace(/\bNaN\b/g, 'null'))
      } catch (parseErr) {
        throw new Error('接口返回的是字符串，但 JSON 解析失败')
      }
    }

    // 1. 分情况提取 features（严格校验结构）
    let features = []
    if (data?.type === 'FeatureCollection') {
      // 情况1：标准 GeoJSON FeatureCollection
      if (Array.isArray(data.features)) {
        features = data.features
      } else {
        throw new Error('FeatureCollection 中 features 不是数组')
      }
    } else if (Array.isArray(data)) {
      // 情况2：直接返回 features 数组
      features = data
    } else {
      // 情况3：其他非法结构
      throw new Error(`接口结构异常，响应：${JSON.stringify(data)}`)
    }

    // 2. 提取 properties 数据（确保非空）
    sampleData.value = features.map(f => f.properties)
    if (sampleData.value.length === 0) {
      throw new Error('接口返回数据为空（properties 为空）')
    }
    log(`成功提取 ${sampleData.value.length} 条数据`, '接口')

    // 3. 初始化图表
    initChart()

  } catch (err) {
    error.value = err
    isLoading.value = false
    console.error('接口请求失败：', err)
  }
})


    return {
      chartOption,
      isLoading,
      error,
      totalPoints
    }
  }
}
</script>

<style scoped>
.boxplot-container {
  width: 100%;
  height: 300px;
  max-width: 1000px;
  margin: 0 auto;
}
.header { 
  text-align: left;
  margin-bottom: 20px; 
}
.chart-title {
  font-size: 14px;
  color: #2980b9;
  font-weight: 600;
}
.header p { 
  font-size: 0.6rem; 
  color: #666;
  margin: 0; 
}
.loading-state { 
  text-align: center;
  padding: 40px 0;
  color: #666; 
}
.loading-state .spinner {
  display: inline-block; 
  width: 24px; 
  height: 24px; 
  margin-right: 8px;
  border: 3px solid #ccc; 
  border-top-color: #1890ff; 
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
@keyframes spin { 
  to { transform: rotate(360deg); } 
}
.error-state { 
  text-align: center; 
  padding: 40px 0; 
  color: #f56c6c; 
}
.chart-wrapper { 
  width: 100%; 
  height: 220px; /* 确保高度有效 */
}
.chart-container {
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
  padding: 20px;
  margin-bottom: 25px;
}

.sample-subtitle {
  font-size: 0.85rem;
  color: #888;
  margin-top: 4px;
}

</style>