﻿<template>
  <el-card class="dip-chart-card" shadow="never">
    <template #header>
      <div class="card-header">
        <div class="card-header-main">
          <div class="card-title">耐受图</div>
          <div class="card-subtitle">
            {{ selectedMappingText }}
          </div>
        </div>

        <div class="header-actions">
          <el-button type="primary" plain :icon="Download" :disabled="!hasChartData" @click="downloadChartImage">
            下载图片
          </el-button>
          <el-button type="primary" plain :icon="Document" :disabled="!hasChartData" @click="exportChartData">
            导出数据
          </el-button>
          <el-button
            v-if="!props.autoDrawCurve"
            type="primary"
            plain
            class="draw-curve-button"
            @click="drawCharacteristicCurve"
          >
            绘制特性曲线
          </el-button>
        </div>
      </div>
    </template>

    <div class="chart-wrapper">
      <MyEchart ref="chartRef" :options="chartOptions" />
    </div>
  </el-card>
</template>

<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Document, Download } from '@element-plus/icons-vue'
import * as XLSX from 'xlsx'
import MyEchart from '@/components/echarts/line/index.vue'

type ChartPointStatus = 'pass' | 'fail'

interface ChartPoint {
  duration: number
  residualVoltage: number
  status: ChartPointStatus
}

interface NormalizedTolerantPoint {
  duration: number
  residualVoltage: number
  tolerant: number | null
  status: ChartPointStatus
}

const props = defineProps<{
  selectedMapping?: Record<string, any> | null
  webMsgSend?: any
  resultData?: any
  autoDrawCurve?: boolean
}>()

const STATUS_COLOR_MAP: Record<ChartPointStatus, string> = {
  pass: '#4e73df',
  fail: '#4b4b4b'
}

const CHARACTERISTIC_POINT_COLOR = '#ff4d4f'

const chartPoints = ref<ChartPoint[]>([])
const characteristicCurveData = ref<Array<[number, number]>>([])
const characteristicCurveVisible = ref(false)
const chartRef = ref<any>(null)

const selectedMappingText = computed(() => {
  if (!props.selectedMapping) {
    return '未选择变频器'
  }

  return `变频器：${props.selectedMapping.freqConverterName || '-'}`
})

const positiveDurations = computed(() => {
  return [
    ...chartPoints.value.map(item => item.duration),
    ...characteristicCurveData.value.map(item => item[0])
  ].filter(item => Number.isFinite(item) && item > 0)
})

const xAxisMin = computed(() => {
  return 0.001
  // if (!positiveDurations.value.length) {
  //   return 0.001
  // }
  //
  // const minValue = Math.min(...positiveDurations.value)
  // return Math.min(0.001, Number(minValue.toFixed(3)))
})

const xAxisMax = computed(() => {
  return 1000
  // if (!positiveDurations.value.length) {
  //   return 60
  // }
  //
  // const maxValue = Math.max(...positiveDurations.value)
  // return Math.max(Number((maxValue * 1.05).toFixed(3)), 60)
})

const sortedChartPoints = computed(() => {
  return [...chartPoints.value].sort((a, b) => {
    if (a.duration !== b.duration) {
      return a.duration - b.duration
    }

    return a.residualVoltage - b.residualVoltage
  })
})

const sortedCharacteristicCurveData = computed(() => {
  return [...characteristicCurveData.value].sort((a, b) => {
    if (a[0] !== b[0]) {
      return a[0] - b[0]
    }

    return a[1] - b[1]
  })
})

const solidCharacteristicCurveSeriesData = computed(() => {
  if (!characteristicCurveVisible.value) {
    return [] as Array<[number, number, string]>
  }

  return sortedCharacteristicCurveData.value.map(item => [item[0], item[1], '特性曲线'])
})

const characteristicCurvePointSeriesData = computed(() => {
  return sortedCharacteristicCurveData.value.map(item => ({
    value: [item[0], item[1], '特性点']
  }))
})

const passPointSeriesData = computed(() => {
  return sortedChartPoints.value
    .filter(item => item.status === 'pass')
    .map(item => ({
      value: [item.duration, item.residualVoltage, getStatusText(item.status)]
    }))
})

const failPointSeriesData = computed(() => {
  return sortedChartPoints.value
    .filter(item => item.status === 'fail')
    .map(item => ({
      value: [item.duration, item.residualVoltage, getStatusText(item.status)]
    }))
})

const hasChartData = computed(() => {
  return sortedChartPoints.value.length > 0 || sortedCharacteristicCurveData.value.length > 0
})

const formatLogDurationLabel = (value: number) => {
  if (!Number.isFinite(value)) {
    return ''
  }

  if (value >= 1) {
    return `${Number(value.toFixed(2))}`
  }

  return `${Number(value.toFixed(3))}`
}

const sanitizeFileName = (value: string) => {
  return value.replace(/[\\/:*?"<>|]/g, '_').trim() || '未命名变频器'
}

const buildFileName = (prefix: string, suffix: string) => {
  const freqConverterName = sanitizeFileName(props.selectedMapping?.freqConverterName || '未命名变频器')
  return `${prefix}_${freqConverterName}.${suffix}`
}

const triggerDownload = (url: string, fileName: string) => {
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.download = fileName
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}

const toNumber = (value: unknown) => {
  const result = Number(value)
  return Number.isFinite(result) ? result : null
}

const normalizeTolerantValue = (value: unknown) => {
  if (value === undefined || value === null || value === '') {
    return null
  }

  const result = Number(value)
  if ([0, 1, 2].includes(result)) {
    return result
  }

  return null
}

const normalizeDuration = (source: Record<string, any>) => {
  return toNumber(
    source.durationMs !== undefined && source.durationMs !== null
      ? Number(source.durationMs) / 1000
      : source.duration ?? source.x ?? source.dipDuration ?? source.retainTime ?? source.durationValue
  )
}

const normalizeResidualVoltageValue = (source: Record<string, any>) => {
  return toNumber(source.residualVoltage ?? source.y ?? source.residual ?? source.voltage ?? source.residual_value)
}

const normalizeStatus = (value: unknown): ChartPointStatus => {
  const rawValue = `${value ?? ''}`.trim().toLowerCase()

  if (
    value === 0 ||
    rawValue === '0' ||
    rawValue === 'false' ||
    rawValue === 'fail' ||
    rawValue === 'failed' ||
    rawValue.includes('不耐受')
  ) {
    return 'fail'
  }

  return 'pass'
}

const normalizeTolerantPoint = (source: Record<string, any>): NormalizedTolerantPoint | null => {
  const duration = normalizeDuration(source)
  const residualVoltage = normalizeResidualVoltageValue(source)

  if (duration === null || residualVoltage === null) {
    return null
  }

  if (duration <= 0 || residualVoltage < 0 || residualVoltage > 100) {
    return null
  }

  const tolerant = normalizeTolerantValue(
    source.tolerant ??
      source.endure ??
      source.isEndure ??
      source.tolerable ??
      source.isTolerable ??
      source.status ??
      source.pointStatus ??
      source.result ??
      source.state
  )

  return {
    duration,
    residualVoltage,
    tolerant,
    status:
      tolerant === 0
        ? 'fail'
        : tolerant === 1
          ? 'pass'
          : normalizeStatus(
              source.tolerant ??
                source.endure ??
                source.isEndure ??
                source.tolerable ??
                source.isTolerable ??
                source.status ??
                source.pointStatus ??
                source.result ??
                source.state
            )
  }
}

const getStatusText = (status: ChartPointStatus) => {
  return status === 'fail' ? '不耐受' : '耐受'
}

const normalizePoint = (source: Record<string, any>): ChartPoint | null => {
  const point = normalizeTolerantPoint(source)
  if (!point || point.tolerant === 2) {
    return null
  }

  return {
    duration: point.duration,
    residualVoltage: point.residualVoltage,
    status: point.status
  }
}

const extractCharacteristicCurvePoints = (payload: any) => {
  const result: Array<[number, number]> = []
  const seen = new Set<string>()
  const rootPayload = payload?.data && typeof payload.data === 'object' ? payload.data : payload

  const walk = (node: any) => {
    if (!node) {
      return
    }

    if (Array.isArray(node)) {
      node.forEach(item => walk(item))
      return
    }

    if (typeof node !== 'object') {
      return
    }

    const point = normalizeTolerantPoint(node)
    if (point?.tolerant === 2) {
      const key = `${point.duration}|${point.residualVoltage}`
      if (!seen.has(key)) {
        seen.add(key)
        result.push([point.duration, point.residualVoltage])
      }
    }

    Object.values(node).forEach(item => {
      if (item && typeof item === 'object') {
        walk(item)
      }
    })
  }

  walk(rootPayload)
  return result
}

const mergeCharacteristicCurvePoints = (points: Array<[number, number]>) => {
  if (!points.length) {
    return
  }

  const existingPointMap = new Map(
    characteristicCurveData.value.map(item => [`${item[0]}|${item[1]}`, item] as const)
  )

  points.forEach(item => {
    existingPointMap.set(`${item[0]}|${item[1]}`, item)
  })

  characteristicCurveData.value = Array.from(existingPointMap.values())
}

const extractPoints = (payload: any) => {
  const result: ChartPoint[] = []
  const seen = new Set<string>()
  const rootPayload = payload?.data && typeof payload.data === 'object' ? payload.data : payload

  const walk = (node: any) => {
    if (!node) {
      return
    }

    if (Array.isArray(node)) {
      node.forEach(item => walk(item))
      return
    }

    if (typeof node !== 'object') {
      return
    }

    const point = normalizePoint(node)
    if (point) {
      const key = `${point.duration}|${point.residualVoltage}`
      if (!seen.has(key)) {
        seen.add(key)
        result.push(point)
      }
    }

    Object.values(node).forEach(item => {
      if (item && typeof item === 'object') {
        walk(item)
      }
    })
  }

  walk(rootPayload)
  return result
}

const updateCharacteristicCurveVisibility = () => {
  if (props.autoDrawCurve) {
    characteristicCurveVisible.value = characteristicCurveData.value.length > 0
  }
}

const drawCharacteristicCurve = () => {
  if (!sortedCharacteristicCurveData.value.length) {
    characteristicCurveVisible.value = false
    ElMessage.warning('暂无特性曲线点')
    return
  }

  characteristicCurveVisible.value = true
}

const downloadChartImage = async () => {
  if (!hasChartData.value) {
    ElMessage.warning('暂无可下载的图表数据')
    return
  }

  await nextTick()

  const chartInstance = chartRef.value?.getChartInstance?.()
  if (!chartInstance) {
    ElMessage.warning('图表尚未渲染完成')
    return
  }

  const imageUrl = chartInstance.getDataURL({
    type: 'png',
    pixelRatio: 2,
    backgroundColor: '#ffffff'
  })

  triggerDownload(imageUrl, buildFileName('变频器耐受图', 'png'))
  ElMessage.success('图表图片导出成功')
}

const exportChartData = () => {
  if (!hasChartData.value) {
    ElMessage.warning('暂无可导出的点位数据')
    return
  }

  const workbook = XLSX.utils.book_new()

  if (sortedChartPoints.value.length) {
    const pointSheet = XLSX.utils.json_to_sheet(
      sortedChartPoints.value.map((item, index) => ({
        序号: index + 1,
        持续时间_s: item.duration,
        残余电压_pct: item.residualVoltage,
        状态: getStatusText(item.status)
      }))
    )
    XLSX.utils.book_append_sheet(workbook, pointSheet, '耐受点')
  }

  if (sortedCharacteristicCurveData.value.length) {
    const curveSheet = XLSX.utils.json_to_sheet(
      sortedCharacteristicCurveData.value.map((item, index) => ({
        序号: index + 1,
        持续时间_s: item[0],
        残余电压_pct: item[1]
      }))
    )
    XLSX.utils.book_append_sheet(workbook, curveSheet, '特性点')
  }

  const workbookBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
  const blob = new Blob([workbookBuffer], {
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  })
  const blobUrl = window.URL.createObjectURL(blob)

  try {
    triggerDownload(blobUrl, buildFileName('变频器耐受图数据', 'xlsx'))
    ElMessage.success('点位数据导出成功')
  } finally {
    window.URL.revokeObjectURL(blobUrl)
  }
}

const chartOptions = computed(() => {
  return {
    title: {
      text: ''
    },
    grid: {
      top: 30,
      left: 48,
      right: 22,
      bottom: 52
    },
    tooltip: {
      trigger: 'item',
      formatter(params: any) {
        const rawValue = Array.isArray(params.value) ? params.value : params.value?.value
        if (!Array.isArray(rawValue)) {
          return ''
        }

        const [duration, residualVoltage, statusText] = rawValue
        return [
          `类型: ${params.seriesName}`,
          `持续时间: ${duration} s`,
          `残余电压: ${residualVoltage} %`,
          ...(statusText ? [`状态: ${statusText}`] : [])
        ].join('<br/>')
      }
    },
    legend: {
      top: 0,
      right: 0,
      data: ['特性曲线', '特性点', '耐受点', '不耐受点']
    },
    xAxis: {
      type: 'log',
      name: '持续时间(s)',
      nameLocation: 'middle',
      nameGap: 34,
      min: xAxisMin.value,
      max: xAxisMax.value,
      logBase: 10,
      minorTick: {
        show: true,
        splitNumber: 10
      },
      minorSplitLine: {
        show: false,
        lineStyle: {
          color: '#e8edf6'
        }
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: '#cfd8e6'
        }
      },
      axisLabel: {
        formatter(value: number) {
          return formatLogDurationLabel(value)
        }
      }
    },
    yAxis: {
      type: 'value',
      name: '残余电压',
      min: 0,
      max: 100,
      interval: 10,
      minorTick: {
        show: true,
        splitNumber: 2
      },
      minorSplitLine: {
        show: true,
        lineStyle: {
          color: '#e8edf6'
        }
      },
      splitLine: {
        lineStyle: {
          color: '#cfd8e6'
        }
      },
      axisLabel: {
        formatter(value: number) {
          return `${value}%`
        }
      }
    },
    dataZoom: [],
    series: [
      {
        name: '特性曲线',
        type: 'line',
        smooth: true,
        showSymbol: false,
        lineStyle: {
          color: CHARACTERISTIC_POINT_COLOR,
          width: 3
        },
        itemStyle: {
          color: CHARACTERISTIC_POINT_COLOR
        },
        data: solidCharacteristicCurveSeriesData.value
      },
      {
        name: '特性点',
        type: 'scatter',
        symbolSize: 10,
        itemStyle: {
          color: CHARACTERISTIC_POINT_COLOR
        },
        data: characteristicCurvePointSeriesData.value
      },
      {
        name: '耐受点',
        type: 'scatter',
        symbolSize: 10,
        itemStyle: {
          color: STATUS_COLOR_MAP.pass
        },
        data: passPointSeriesData.value
      },
      {
        name: '不耐受点',
        type: 'scatter',
        symbolSize: 10,
        itemStyle: {
          color: STATUS_COLOR_MAP.fail
        },
        data: failPointSeriesData.value
      }
    ],
    options: {
      animation: false
    }
  }
})

watch(
  () => props.autoDrawCurve,
  newValue => {
    characteristicCurveVisible.value = newValue ? characteristicCurveData.value.length > 0 : false
  },
  { immediate: true }
)

watch(
  () => props.webMsgSend,
  newValue => {
    if (!newValue) {
      return
    }

    const nextPoints = extractPoints(newValue)
    if (nextPoints.length) {
      const existingPointMap = new Map(
        chartPoints.value.map(item => [`${item.duration}|${item.residualVoltage}`, item] as const)
      )

      nextPoints.forEach(item => {
        const key = `${item.duration}|${item.residualVoltage}`
        existingPointMap.set(key, item)
      })

      chartPoints.value = Array.from(existingPointMap.values())
    }

    mergeCharacteristicCurvePoints(extractCharacteristicCurvePoints(newValue))
    updateCharacteristicCurveVisibility()
  },
  { deep: true }
)

watch(
  () => props.resultData,
  newValue => {
    if (!newValue) {
      return
    }

    chartPoints.value = extractPoints(newValue)
    characteristicCurveData.value = extractCharacteristicCurvePoints(newValue)
    updateCharacteristicCurveVisibility()
  },
  { deep: true, immediate: true }
)

watch(
  () => props.selectedMapping,
  () => {
    chartPoints.value = []
    characteristicCurveData.value = []
    characteristicCurveVisible.value = false
  }
)
</script>

<style scoped>
.dip-chart-card {
  border: 1px solid var(--el-border-color-light);
}

:deep(.dip-chart-card .el-card__header) {
  padding: 10px 14px;
}

:deep(.dip-chart-card .el-card__body) {
  padding: 10px 14px 14px;
}

.card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
}

.card-header-main {
  min-width: 0;
}

.card-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--el-text-color-primary);
}

.card-subtitle {
  margin-top: 4px;
  font-size: 13px;
  color: var(--el-text-color-secondary);
}

.chart-wrapper {
  height: 400px;
}
</style>
