<script lang="ts" setup>
import { IotDeviceApi } from '@/api/pms/device'
import { cancelAllRequests, IotStatApi } from '@/api/pms/stat'
import { useMqtt } from '@/utils/useMqtt'
import { Dimensions, formatIotValue } from '@/utils/useSocketBus'
import dayjs from 'dayjs'
import * as echarts from 'echarts'
import { neonColors } from '@/utils/td-color'

const props = defineProps({
  id: {
    type: Number,
    required: true
  },
  deviceCode: {
    type: String,
    required: true
  },
  deviceName: {
    type: String,
    required: true
  },
  mqttUrl: {
    type: String,
    required: true
  },
  ifInline: {
    type: String,
    required: true
  },
  lastInlineTime: {
    type: String,
    required: true
  },
  deptName: {
    type: String,
    required: true
  },
  vehicleName: {
    type: String,
    required: true
  },
  carOnline: {
    type: String,
    required: true
  },
  // isRealTime: {
  //   type: Boolean,
  //   default: true
  // },
  date: {
    type: Array as PropType<Array<string>>,
    required: true
  },
  token: {
    type: String,
    required: true
  }
})

const dimensions = ref<Omit<Dimensions, 'color' | 'bgHover' | 'bgActive'>[]>([])
const selectedDimension = ref<Record<string, boolean>>({})

const { connect, destroy, isConnected, subscribe } = useMqtt()

const handleMessageUpdate = (_topic: string, data: any) => {
  const valueMap = new Map<string, number>()

  for (const item of data) {
    const { id: identity, value: logValue, remark } = item

    const value = logValue ? Number(logValue) : 0

    if (identity) {
      valueMap.set(identity, value)
    }

    const modelName = dimensions.value.find((item) => item.identifier === identity)?.name

    if (modelName && chartData.value[modelName]) {
      chartData.value[modelName].push({
        ts: dayjs.unix(remark).valueOf(),
        value
      })

      updateSingleSeries(modelName)
    }
  }
}

watch(isConnected, (newVal) => {
  if (newVal) {
    // subscribe(`/636/${props.deviceCode}/property/post`)

    // switch (props.deviceCode) {
    //   case 'YF1539':
    //     subscribe(`/656/${props.deviceCode}/property/post`)
    //   case 'YF325':
    //   case 'YF288':
    //   case 'YF671':
    //   case 'YF459':
    //     subscribe(`/635/${props.deviceCode}/property/post`)
    //   case 'YF649':
    //     subscribe(`/636/${props.deviceCode}/property/post`)
    //   default:
    //     subscribe(`/636/${props.deviceCode}/property/post`)
    // }

    subscribe(props.mqttUrl)
    // subscribe('/636/YF649/property/post')
  }
})

async function loadDimensions() {
  if (!props.id) return
  try {
    dimensions.value = (((await IotDeviceApi.getIotDeviceTds(Number(props.id))) as any[]) ?? [])
      .sort((a, b) => b.modelOrder - a.modelOrder)
      .map((item) => {
        const { value, suffix, isText } = formatIotValue(item.value)
        return {
          identifier: item.identifier,
          name: item.modelName,
          value: value,
          suffix: suffix,
          isText: isText,
          response: false
        }
      })
      .filter((item) => item.isText === false)

    selectedDimension.value = Object.fromEntries(dimensions.value.map((item) => [item.name, true]))
  } catch (error) {
    console.error(error)
  }
}

interface ChartData {
  [key: Dimensions['name']]: { ts: number; value: number }[]
}

const chartData = ref<ChartData>({})

let intervalArr = ref<number[]>([])
let maxInterval = ref(0)
let minInterval = ref(0)

const chartRef = ref<HTMLDivElement | null>(null)
let chart: echarts.ECharts | null = null

function genderIntervalArr(init: boolean = false) {
  // 1. 使用正负无穷大初始化，避免先把所有数存入数组
  let maxVal = -Infinity
  let minVal = Infinity
  let hasData = false

  // 2. 直接遍历数据查找最值 (不使用 spread ...)
  for (const [key, value] of Object.entries(selectedDimension.value)) {
    if (value) {
      const dataset = chartData.value[key]
      if (dataset && dataset.length > 0) {
        hasData = true
        // 使用循环代替 ...spread
        for (const item of dataset) {
          const val = item.value
          if (val > maxVal) maxVal = val
          if (val < minVal) minVal = val
        }
      }
    }
  }

  // 3. 处理无数据的默认情况
  if (!hasData) {
    maxVal = 10000
    minVal = 0
  } else {
    // 保持你原有的逻辑：如果最小值大于0，则归零
    minVal = minVal > 0 ? 0 : minVal
  }

  // 4. 计算位数逻辑 (保持不变)
  const maxDigits = (Math.floor(maxVal) + '').length
  const minDigits = minVal === 0 ? 0 : (Math.floor(Math.abs(minVal)) + '').length

  const interval = Math.max(maxDigits, minDigits)

  maxInterval.value = interval
  minInterval.value = minDigits

  intervalArr.value = [0]
  for (let i = 1; i <= interval; i++) {
    intervalArr.value.push(Math.pow(10, i))
  }

  if (!init) {
    chart?.setOption({
      yAxis: {
        min: -minInterval.value,
        max: maxInterval.value
      }
    })
  }
}

function chartInit() {
  if (!chart) return

  chart.on('legendselectchanged', (params: any) => {
    // 1. 同步选中状态
    selectedDimension.value = params.selected
    const clickedModelName = params.name
    const isSelected = params.selected[clickedModelName]

    const oldMax = maxInterval.value
    const oldMin = minInterval.value

    genderIntervalArr()

    const isScaleChanged = oldMax !== maxInterval.value || oldMin !== minInterval.value

    if (isScaleChanged) {
      Object.keys(selectedDimension.value).forEach((name) => {
        if (selectedDimension.value[name]) {
          updateSingleSeries(name)
        }
      })
    } else {
      if (isSelected) {
        updateSingleSeries(clickedModelName)
      }
    }
  })

  window.addEventListener('resize', () => {
    if (chart) chart.resize()
  })
}

function render() {
  if (!chartRef.value) return

  if (!chart) chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })

  chartInit()

  genderIntervalArr(true)

  chart.setOption({
    color: neonColors,
    animation: true,
    animationDuration: 200,
    animationEasing: 'linear',
    animationDurationUpdate: 200,
    animationEasingUpdate: 'linear',
    grid: {
      left: '3%',
      top: '60px',
      right: '6%',
      bottom: '10%',
      containLabel: true,
      show: false
    },
    tooltip: {
      trigger: 'axis',
      confine: true,
      enterable: true,
      className: 'echarts-tooltip-scroll',
      extraCssText:
        'max-height: 300px; overflow-y: auto; pointer-events: auto; border-radius: 4px;',
      backgroundColor: 'rgba(11, 17, 33, 0.95)',
      borderColor: '#22d3ee',
      borderWidth: 1,
      textStyle: {
        color: '#e2e8f0'
      },
      axisPointer: {
        type: 'cross',
        label: { backgroundColor: '#22d3ee', color: '#000' },
        lineStyle: { color: 'rgba(255,255,255,0.3)', type: 'dashed' }
      },
      formatter: (params: any) => {
        let d = `<div style="font-weight:bold; border-bottom:1px solid rgba(255,255,255,0.1); padding-bottom:5px; margin-bottom:5px;">${params[0].axisValueLabel}</div>`
        const exist: string[] = []
        params = params.filter((el: any) => {
          if (exist.includes(el.seriesName)) return false
          exist.push(el.seriesName)
          return true
        })

        // 优化列表显示，圆点使用原本的颜色
        let item = params.map(
          (
            el: any
          ) => `<div class="flex items-center justify-between mt-1" style="font-size:12px; min-width: 180px;">
            <span style="display:flex; align-items:center;">
                <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background-color:${el.color};margin-right:6px;"></span>
                <span style="color:#cbd5e1">${el.seriesName}</span>
            </span>
            <span style="color:#fff; font-weight:bold; margin-left:10px;">${el.value[2]?.toFixed(2)}</span>
          </div>`
        )

        return d + item.join('')
      }
    },
    xAxis: {
      type: 'time',
      boundaryGap: ['0%', '25%'],
      axisLabel: {
        formatter: (v) => dayjs(v).format('YYYY-MM-DD\nHH:mm:ss'),
        rotate: 0,
        align: 'center',
        color: '#94a3b8',
        fontSize: 11
      },
      splitLine: {
        show: false,
        lineStyle: { color: 'rgba(255,255,255,0.5)', type: 'dashed' }
      }
    },
    dataZoom: [
      { type: 'inside', xAxisIndex: 0 },
      {
        type: 'slider',
        xAxisIndex: 0,
        height: 20,
        bottom: 10,
        borderColor: 'transparent',
        backgroundColor: 'rgba(255,255,255,0.05)',
        fillerColor: 'rgba(34,211,238,0.2)',
        handleStyle: {
          color: '#22d3ee',
          borderColor: '#22d3ee'
        },
        labelFormatter: (value: any) => {
          return dayjs(value).format('YYYY-MM-DD\nHH:mm:ss')
        },
        textStyle: {
          color: '#94a3b8',
          fontSize: 10,
          lineHeight: 12
        }
      }
    ],
    yAxis: {
      type: 'value',
      min: -minInterval.value,
      max: maxInterval.value,
      interval: 1,
      axisLabel: {
        color: '#94a3b8',
        formatter: (v) => {
          const num = v === 0 ? 0 : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v)
          if (Math.abs(num) >= 10000) return (num / 10000).toFixed(0) + 'w'
          if (Math.abs(num) >= 1000) return (num / 1000).toFixed(0) + 'k'
          return num.toLocaleString()
        }
      },
      show: true,
      splitLine: {
        show: true,
        lineStyle: {
          color: 'rgba(255,255,255,0.05)',
          type: 'dashed'
        }
      },
      axisPointer: {
        show: true,
        snap: false, // 必须设为 false，才能平滑显示小数部分的真实值
        label: {
          show: true,
          backgroundColor: '#22d3ee', // 青色背景
          color: '#000', // 黑色文字
          fontWeight: 'bold',
          precision: 2, // 保证精度

          // --- 具体的实现逻辑 ---
          formatter: (params: any) => {
            const val = params.value // 这里拿到的是索引值，比如 4.21
            if (val === 0) return '0.00'

            // A. 处理正负号
            const sign = val >= 0 ? 1 : -1
            const absVal = Math.abs(val)

            // B. 分离 整数部分(区间下标) 和 小数部分(区间内百分比)
            const idx = Math.floor(absVal)

            const percent = absVal - idx

            // C. 安全检查：如果 intervalArr 还没生成，直接返回指数值
            if (!intervalArr.value || intervalArr.value.length === 0) {
              return (sign * Math.pow(10, absVal)).toFixed(2)
            }

            // D. 获取该区间的真实数值范围
            // 例如 idx=2, 对应 intervalArr[2]=100, intervalArr[3]=1000
            const min = intervalArr.value[idx]
            // 如果到了最后一个区间，或者越界，就默认下一级是当前的10倍（防止报错）
            const max =
              intervalArr.value[idx + 1] !== undefined ? intervalArr.value[idx + 1] : min * 10

            // E. 反向线性插值公式
            // 真实值 = 下界 + (上下界之差 * 百分比)
            const realVal = min + (max - min) * percent

            // F. 加上符号并格式化
            return (realVal * sign).toFixed(2)
          }
        }
      }
    },
    legend: {
      type: 'scroll', // 开启滚动，防止遮挡
      top: 10,

      left: 'center',
      width: '90%',

      textStyle: {
        color: '#e2e8f0', // 亮白色
        fontSize: 12
      },
      pageIconColor: '#22d3ee',
      pageIconInactiveColor: '#475569',
      pageTextStyle: { color: '#fff' },
      data: dimensions.value.map((item) => item.name),
      selected: selectedDimension.value,
      show: true
    },
    // legend: {
    //   data: dimensions.value.map((item) => item.name),
    //   selected: selectedDimension.value,
    //   show: true
    // },
    series: dimensions.value.map((item) => ({
      name: item.name,
      type: 'line',
      smooth: 0.3,
      showSymbol: false,

      endLabel: {
        show: true,
        formatter: (params) => params.value[2]?.toFixed(2),
        offset: [4, 0],
        color: '#fff',
        backgroundColor: 'auto',
        padding: [2, 6],
        borderRadius: 4,
        fontSize: 11,
        fontWeight: 'bold'
      },

      emphasis: {
        focus: 'series',
        lineStyle: { width: 4 }
      },

      lineStyle: {
        width: 3,
        shadowColor: 'rgba(0, 0, 0, 0.5)',
        shadowBlur: 10,
        shadowOffsetY: 5
      },

      data: [] // 占位数组
    }))
  })
}

function mapData({ value, ts }) {
  if (value === null || value === undefined || value === 0) return [ts, 0, 0]

  const isPositive = value > 0
  const absItem = Math.abs(value)

  if (!intervalArr.value.length) return [ts, 0, value]

  const min_value = Math.max(...intervalArr.value.filter((v) => v <= absItem))
  const min_index = intervalArr.value.findIndex((v) => v === min_value)

  let denominator = 1
  if (min_index < intervalArr.value.length - 1) {
    denominator = intervalArr.value[min_index + 1] - intervalArr.value[min_index]
  } else {
    denominator = intervalArr.value[min_index] || 1
  }

  const new_value = (absItem - min_value) / denominator + min_index

  return [ts, isPositive ? new_value : -new_value, value]
}

function updateSingleSeries(name: string) {
  if (!chart) render()
  if (!chart) return

  const idx = dimensions.value.findIndex((item) => item.name === name)
  if (idx === -1) return

  const data = chartData.value[name].map((v) => mapData(v))

  chart.setOption({
    series: [{ name, data }]
  })
}

const lastTsMap = ref<Record<Dimensions['name'], number>>({})

const chartLoading = ref(false)

async function initLoadChartData(real_time: boolean = true) {
  if (!dimensions.value.length) return

  chartData.value = Object.fromEntries(dimensions.value.map((item) => [item.name, []]))

  chartLoading.value = true

  dimensions.value = dimensions.value.map((item) => {
    item.response = true
    return item
  })

  for (const item of dimensions.value) {
    const { identifier, name } = item
    try {
      const res = await IotStatApi.getDeviceInfoChart(
        props.deviceCode,
        identifier,
        props.date[0],
        props.date[1]
      )

      const sorted = res
        .sort((a, b) => a.ts - b.ts)
        .map((item) => ({ ts: item.ts, value: item.value }))

      chartData.value[name] = sorted

      lastTsMap.value[name] = sorted.at(-1)?.ts ?? 0

      genderIntervalArr()

      updateSingleSeries(name)

      chartLoading.value = false
    } finally {
      item.response = false
    }
  }

  if (real_time) {
    connect(`wss://aims.deepoil.cc/mqtt`, { password: props.token }, handleMessageUpdate)
  }
}

async function initfn(load: boolean = true, real_time: boolean = true) {
  if (load) await loadDimensions()
  render()
  initLoadChartData(real_time)
}

onMounted(() => {
  initfn()
})

watch(
  () => props.date,
  async (newDate, oldDate) => {
    if (!newDate || newDate.length !== 2) return

    if (oldDate && newDate[0] === oldDate[0] && newDate[1] === oldDate[1]) return

    await cancelAllRequests()

    destroy()

    const endTime = dayjs(newDate[1])
    const now = dayjs()
    const isRealTime = endTime.isAfter(now.subtract(1, 'minute'))

    if (chart) chart.clear()

    if (isRealTime) initfn(false)
    else initfn(false, false)
  }
)

onUnmounted(() => {
  destroy()

  window.removeEventListener('resize', () => {
    if (chart) chart.resize()
  })
})

const router = useRouter()

function handleDetailClick() {
  router.push({
    name: 'MonitoringDetail',
    query: {
      id: props.id,
      ifInline: props.ifInline,
      carOnline: props.carOnline,
      time: props.lastInlineTime,
      name: props.deviceName,
      code: props.deviceCode,
      dept: props.deptName,
      vehicle: props.vehicleName,
      mqttUrl: props.mqttUrl
    }
  })
}
</script>
<template>
  <div class="h-100 rounded-lg chart-container flex flex-col">
    <header class="chart-header justify-between">
      <div class="flex items-center">
        <div class="title-icon"></div>
        <div>{{ `${props.deviceCode}-${props.deviceName}` }}</div>
      </div>
      <el-button link type="primary" class="group" @click="handleDetailClick">
        详情
        <div
          class="i-material-symbols:arrow-right-alt-rounded size-4 transition-transform group-hover:translate-x-1"
        ></div>
      </el-button>
    </header>
    <main
      class="flex-1 chart-main"
      ref="chartRef"
      v-loading="chartLoading"
      element-loading-background="transparent"
    ></main>
  </div>
</template>
<style scoped>
.chart-container {
  position: relative;
  overflow: hidden;
  background-color: rgb(11 17 33 / 90%);
  border: 2px solid rgb(34 211 238 / 30%);
  box-shadow:
    0 0 20px rgb(0 0 0 / 80%),
    inset 0 0 15px rgb(34 211 238 / 10%);
  transition:
    border-color 0.3s ease,
    transform 0.3s ease;
}

.chart-container::before {
  position: absolute;
  pointer-events: none;
  background: radial-gradient(
    400px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
    rgb(34 211 238 / 5%),
    transparent 40%
  );
  content: '';
  inset: 0;
}

.chart-container:hover {
  border-color: rgb(34 211 238 / 60%);
  transform: scale(1.005);
}

.chart-header {
  display: flex;
  padding: 12px 16px;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 1px;
  color: #e2e8f0;
  background: rgb(255 255 255 / 3%);
  border-bottom: 1px solid transparent;
  border-image: linear-gradient(to right, rgb(34 211 238 / 50%), transparent) 1;
  align-items: center;
}

.title-icon {
  width: 4px;
  height: 16px;
  margin-right: 10px;
  background: #22d3ee;
  box-shadow: 0 0 8px #22d3ee;
}

.chart-main {
  padding-top: 12px;
  background-image: radial-gradient(circle at 50% 50%, rgb(34 211 238 / 10%) 0%, transparent 80%),
    linear-gradient(to right, rgb(34 211 238 / 15%) 1px, transparent 1px),
    linear-gradient(to bottom, rgb(34 211 238 / 15%) 1px, transparent 1px),
    linear-gradient(135deg, rgb(11 17 33 / 90%) 0%, rgb(6 9 18 / 95%) 100%);
  background-size:
    100% 100%,
    40px 40px,
    40px 40px,
    100% 100%;
}

/* 针对 ECharts tooltip 的滚动条美化 */
.echarts-tooltip-scroll::-webkit-scrollbar {
  width: 6px;
}

.echarts-tooltip-scroll::-webkit-scrollbar-thumb {
  background: #22d3ee; /* 青色滑块 */
  border-radius: 3px;
}

.echarts-tooltip-scroll::-webkit-scrollbar-track {
  background: rgb(255 255 255 / 10%); /* 深色轨道 */
}
</style>
