<script setup lang="ts">
import * as echarts from 'echarts'
import { Odometer, CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import { rangeShortcuts } from '@/utils/formatTime'

import dayjs from 'dayjs'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import { IotDeviceApi, cancelAllRequests } from '@/api/pms/device'
import { IotStatApi } from '@/api/pms/stat'

defineOptions({ name: 'TdDeviceDetail' })

dayjs.extend(quarterOfYear)

const { params } = useRoute()

const data = ref({
  deviceCode: params.code || '',
  deviceName: params.name || '',
  lastInlineTime: params.time || '',
  ifInline: params.ifInline || '',
  dept: params.dept || '',
  vehicle: params.vehicle || '',
  carOnline: params.carOnline || ''
})

const disabledIdentifier = ref<string[]>(['online', 'vehicle_name', 'touchScreenDataAccumulate'])

const specs = ref<any[]>([])
const gatewayspecs = ref<any[]>([])
const zhbdspecs = ref<any[]>([])
const selectSpec = ref<Record<string, boolean>>({})
const chartMap = ref<Record<string, { name: string; value: any[] }>>({})

const specsLoading = ref(false)

const lastTsMap = ref<Record<string, number>>({})

// 每 10s 刷新定时器
const timer = ref<NodeJS.Timeout | null>(null)

const defaultDate = rangeShortcuts[0].value()
const date = ref([
  dayjs(defaultDate[0]).format('YYYY-MM-DD HH:mm:ss'),
  dayjs(defaultDate[1]).format('YYYY-MM-DD HH:mm:ss')
])

const reset = () => {
  cancelAllRequests().then(() => {
    const def = rangeShortcuts[0].value()

    date.value = [
      dayjs(def[0]).format('YYYY-MM-DD HH:mm:ss'),
      dayjs(def[1]).format('YYYY-MM-DD HH:mm:ss')
    ]
    stopAutoFetch()
    if (chart) chart.clear()
    render()
    initLoad()
  })
}

const handleDateChange = () => {
  cancelAllRequests().then(() => {
    stopAutoFetch()
    if (chart) chart.clear()
    render()
    initLoad(false)
  })
}

const handleClickSpec = (modelName: string) => {
  selectSpec.value[modelName] = !selectSpec.value[modelName]
  chart?.setOption({
    legend: {
      selected: selectSpec.value
    }
  })
}

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

const chartInit = () => {
  if (!chart) return

  chart.on('legendselectchanged', (params: any) => {
    selectSpec.value = params.selected
  })

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

// 映射区间相关
let intervalArr: number[] = []
let maxInterval = 0
let minInterval = 0

// 1. 加载 specs
const loadSpecs = async () => {
  if (!params.id) return
  specsLoading.value = true
  const res = await IotDeviceApi.getIotDeviceTds(Number(params.id))
  const zhbdres = await IotDeviceApi.getIotDeviceZHBDTds(Number(params.id))

  zhbdspecs.value = zhbdres.sort((a, b) => b.modelOrder - a.modelOrder)
  gatewayspecs.value = res.sort((a, b) => b.modelOrder - a.modelOrder)

  specs.value = [
    ...JSON.parse(JSON.stringify(gatewayspecs.value)),
    ...JSON.parse(JSON.stringify(zhbdspecs.value))
  ]

  selectSpec.value = specs.value.reduce(
    (acc, spec) => {
      acc[spec.modelName] =
        gatewayspecs.value.some((item) => item.modelName === spec.modelName) &&
        !disabledIdentifier.value.includes(spec.identifier)
      return acc
    },
    {} as Record<string, boolean>
  )

  specsLoading.value = false

  chartMap.value = specs.value
    .filter((spec) => !disabledIdentifier.value.includes(spec.identifier))
    .reduce(
      (acc, spec) => {
        acc[spec.identifier] = { name: spec.modelName, value: [] }
        return acc
      },
      {} as Record<string, { name: string; value: any[] }>
    )
}

const chartLoading = ref(false)

const initLoad = async (real_time: boolean = true) => {
  if (!specs.value.length) return

  Object.keys(chartMap.value).forEach((identifier) => {
    chartMap.value[identifier].value = []
    lastTsMap.value[identifier] = 0
  })

  chartLoading.value = true

  for (const identifier of Object.keys(chartMap.value)) {
    const res = await IotStatApi.getDeviceInfoChart(
      data.value.deviceCode,
      identifier,
      date.value[0],
      date.value[1]
    )

    const sorted = res.sort((a, b) => a.ts - b.ts)
    chartMap.value[identifier].value = sorted
    lastTsMap.value[identifier] = sorted.at(-1)?.ts ?? 0

    updateSingleSeries(identifier)

    chartLoading.value = false
  }

  if (real_time) startAutoFetch()
}

const startAutoFetch = () => {
  timer.value = setInterval(fetchIncrementData, 10000)
}

const stopAutoFetch = () => {
  if (timer.value) clearInterval(timer.value)
  timer.value = null
}

const fetchIncrementData = () => {
  for (const identifier of Object.keys(chartMap.value)) {
    const lastTs = lastTsMap.value[identifier]
    if (!lastTs) continue

    IotStatApi.getDeviceInfoChart(
      data.value.deviceCode,
      identifier,
      dayjs(lastTs).format('YYYY-MM-DD HH:mm:ss'),
      dayjs().format('YYYY-MM-DD HH:mm:ss')
    ).then((res) => {
      if (!res.length) return

      const sorted = res.sort((a, b) => a.ts - b.ts)

      // push 到本地
      chartMap.value[identifier].value.push(...sorted)
      // 更新 lastTs
      lastTsMap.value[identifier] = sorted.at(-1).ts

      appendToSeries(identifier, chartMap.value[identifier].value)
    })
  }
}

const render = () => {
  if (!chartRef.value) return

  if (!chart) chart = echarts.init(chartRef.value)

  chartInit()

  const values = Object.values(chartMap.value).flatMap((item) => item.value.map((d) => d.value))

  const maxVal = Math.max(...values)
  const minVal = Math.min(...values, -100)

  const maxDigits = (Math.floor(maxVal) + '').length
  const minDigits = (Math.floor(Math.abs(minVal)) + '').length
  const interval = Math.max(maxDigits, minDigits)

  maxInterval = interval
  minInterval = minDigits

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

  chart.setOption({
    tooltip: {
      trigger: 'axis',
      formatter: (params) => {
        let d = `${params[0].axisValueLabel}<br>`
        let item = params.map((el) => `${el.marker} ${el.seriesName}: ${el.value[2]}<br>`)
        return d + item.join('')
      }
    },
    dataZoom: [
      { type: 'inside', xAxisIndex: 0 },
      { type: 'slider', xAxisIndex: 0 }
    ],
    xAxis: {
      type: 'time',
      axisLabel: {
        formatter: (v) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
        rotate: 20
      },
      inverse: true
    },
    yAxis: {
      type: 'value',
      min: -minInterval,
      max: maxInterval,
      interval: 1,
      axisLabel: {
        formatter: (v) => (v === 0 ? '0' : v > 0 ? Math.pow(10, v) : -Math.pow(10, -v))
      }
    },
    legend: {
      data: Object.values(chartMap.value).map((i) => i.name),
      selected: selectSpec.value
    },
    series: Object.keys(chartMap.value).map((identifier) => ({
      name: chartMap.value[identifier].name,
      type: 'line',
      smooth: true,
      showSymbol: false,
      data: [] // 占位数组
    }))
  })
}

const updateSingleSeries = (identifier: string) => {
  if (!chart) render()
  if (!chart) return

  const idx = Object.keys(chartMap.value).indexOf(identifier)
  if (idx === -1) return

  const data = chartMap.value[identifier].value.map((v) => mapData(v))

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

const appendToSeries = (identifier, list) => {
  if (!chart) return

  const idx = Object.keys(chartMap.value).indexOf(identifier)
  if (idx === -1) return

  const data = list.map(mapData)

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

const mapData = ({ value, ts }) => {
  if (value === 0) return [ts, 0, 0]

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

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

  const new_value =
    (absItem - min_value) / (intervalArr[min_index + 1] - intervalArr[min_index]) + min_index

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

onMounted(async () => {
  await loadSpecs()
  render()
  initLoad()
})

onUnmounted(() => {
  stopAutoFetch()

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

<template>
  <div
    class="w-full bg-gradient-to-r from-blue-100 to-white rounded-lg p-6 shadow"
    id="td-device-info"
  >
    <h2 class="flex items-center gap-2">
      <el-icon class="text-sky!"><Odometer /></el-icon> 设备基础信息
    </h2>
    <el-form size="large" label-position="top" class="mt-4 grid grid-cols-3 gap-4">
      <el-form-item label="资产编码"> {{ data.deviceCode }} </el-form-item>
      <el-form-item label="设备类别"> {{ data.deviceName }} </el-form-item>
      <el-form-item label="所在部门"> {{ data.dept }} </el-form-item>
      <el-form-item label="网关状态" class="online" type="plain">
        <el-tag
          v-if="data.ifInline === '3'"
          type="success"
          size="default"
          class="flex items-center"
        >
          <el-icon class="text-emerald!"><CircleCheckFilled /></el-icon>
          在线
        </el-tag>

        <el-tag v-if="data.ifInline === '4'" type="danger" size="default" class="flex items-center">
          <el-icon class="text-rose"><CircleCloseFilled /></el-icon>
          离线
        </el-tag>
      </el-form-item>
      <el-form-item label="中航北斗" class="online" type="plain">
        <el-tag
          v-if="data.carOnline === 'true'"
          type="success"
          size="default"
          class="flex items-center"
        >
          <el-icon class="text-emerald!"><CircleCheckFilled /></el-icon>
          在线
        </el-tag>

        <el-tag
          v-if="data.carOnline === 'false'"
          type="danger"
          size="default"
          class="flex items-center"
        >
          <el-icon class="text-rose"><CircleCloseFilled /></el-icon>
          离线
        </el-tag>
      </el-form-item>
      <el-form-item label="最后数据时间"> {{ data.lastInlineTime }} </el-form-item>
      <el-form-item v-if="data.vehicle" label="车牌号码"> {{ data.vehicle }} </el-form-item>
    </el-form>
  </div>
  <div class="mt-4 w-full rounded-lg bg-gradient-to-r from-blue-100 to-white p-4 shadow min-h-50">
    <header class="font-medium text-center w-full">网关数采</header>
    <div
      v-loading="specsLoading"
      element-loading-background="transparent"
      class="w-full mt-4 grid grid-cols-4 gap-4 min-h-30"
      id="dimension"
    >
      <div
        v-for="item in gatewayspecs"
        :key="item.productId"
        class="h-12 bg-white dark:bg-[#1d1e1f] rounded-lg px-6 shadow flex items-center hover:scale-103 transition-all cursor-pointer"
        :class="{
          'bg-blue-200': selectSpec[item.modelName]
        }"
        @click="!disabledIdentifier.includes(item.identifier) && handleClickSpec(item.modelName)"
      >
        <span class="text-sm text-[var(--el-text-color-regular)]">{{ item.modelName }}</span>
        <span class="text-lg font-medium ms-a">{{ item.value }}</span>
      </div>
    </div>
  </div>
  <div
    v-if="zhbdspecs.length"
    class="mt-4 w-full rounded-lg bg-gradient-to-r from-blue-100 to-white p-4 shadow"
  >
    <header class="font-medium text-center w-full">中航北斗</header>
    <div class="w-full mt-4 grid grid-cols-4 gap-4" id="dimension">
      <div
        v-for="item in zhbdspecs"
        :key="item.productId"
        class="h-12 bg-white dark:bg-[#1d1e1f] rounded-lg px-6 shadow flex items-center hover:scale-103 transition-all cursor-pointer"
        :class="{
          'bg-blue-200': selectSpec[item.modelName]
        }"
        @click="!disabledIdentifier.includes(item.identifier) && handleClickSpec(item.modelName)"
      >
        <span class="text-sm text-[var(--el-text-color-regular)]">{{ item.modelName }}</span>
        <span class="text-lg font-medium ms-a">{{ item.value }}</span>
      </div>
    </div>
  </div>
  <div class="w-full mt-4 bg-gradient-to-r from-blue-100 to-white min-h-175 rounded-lg p-6 shadow">
    <header class="flex items-center justify-between">
      <h3 class="flex items-center gap-2">
        <div class="i-material-symbols:area-chart-outline-rounded text-sky size-6" text-sky></div>
        数据趋势
      </h3>

      <div class="flex gap-4">
        <el-button type="primary" size="default" @click="reset">重置</el-button>
        <el-date-picker
          v-model="date"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="datetimerange"
          unlink-panels
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :shortcuts="rangeShortcuts"
          size="default"
          class="w-100!"
          placement="bottom-end"
          @change="handleDateChange"
        />
      </div>
    </header>
    <div
      v-loading="specsLoading"
      element-loading-background="transparent"
      ref="chartRef"
      class="w-full h-158 mt-4 mb-4"
    ></div>
  </div>
</template>

<style lang="scss" scoped>
:deep(.el-form-item) {
  margin-bottom: 0;

  .el-form-item__label {
    margin-bottom: 0;
  }

  .el-form-item__content {
    font-size: 1rem;
    font-weight: 500;
  }

  &.online {
    .el-form-item__content {
      height: 2.5rem;

      .el-tag__content {
        display: flex;
        align-items: center;
        gap: 2px;
      }
    }
  }
}
</style>
