<template>
  <v-chart
    class="fleet-echart"
    :style="{ height: `${height}px` }"
    :option="chartOption"
    :update-options="{ notMerge: true }"
    autoresize
    @datazoom="onDataZoom"
  />
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useTheme } from 'vuetify'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, ScatterChart } from 'echarts/charts'
import {
  DataZoomComponent,
  GridComponent,
  LegendComponent,
  MarkLineComponent,
  MarkPointComponent,
  ToolboxComponent,
  TooltipComponent,
} from 'echarts/components'
import type { ScatterSeriesOption } from 'echarts/charts'
import VChart from 'vue-echarts'
import type { ComposeOption } from 'echarts/core'
import type { LineSeriesOption } from 'echarts/charts'
import type {
  DataZoomComponentOption,
  GridComponentOption,
  LegendComponentOption,
  MarkLineComponentOption,
  MarkPointComponentOption,
  ToolboxComponentOption,
  TooltipComponentOption,
} from 'echarts/components'
import {
  findNearestPointIndex,
  formatPointTimeBeijing,
  pointTimestampMs,
  type FleetAlertThresholds,
  type FleetTrendPoint,
} from '@/utils/fleetTrendAnalytics'
import { parseApiDateTime } from '@/utils/datetime'

const INTERVAL_MS = 10 * 60 * 1000

use([
  CanvasRenderer,
  LineChart,
  ScatterChart,
  GridComponent,
  TooltipComponent,
  LegendComponent,
  DataZoomComponent,
  ToolboxComponent,
  MarkLineComponent,
  MarkPointComponent,
])

type ECOption = ComposeOption<
  | LineSeriesOption
  | ScatterSeriesOption
  | GridComponentOption
  | TooltipComponentOption
  | LegendComponentOption
  | DataZoomComponentOption
  | ToolboxComponentOption
  | MarkLineComponentOption
  | MarkPointComponentOption
>

const props = withDefaults(
  defineProps<{
    points: FleetTrendPoint[]
    hours: number
    thresholds: FleetAlertThresholds
    height?: number
    zoomStart?: number
    zoomEnd?: number
  }>(),
  {
    height: 360,
    zoomStart: undefined,
    zoomEnd: undefined,
  },
)

const emit = defineEmits<{
  'range-change': [payload: { startPercent: number; endPercent: number }]
}>()

const theme = useTheme()

function themeColor(key: 'primary' | 'warning' | 'success' | 'error'): string {
  const c = theme.current.value.colors[key]
  return typeof c === 'string' ? c : '#1976D2'
}

function toSeriesData(field: 'cpu_avg' | 'mem_avg' | 'disk_avg'): [number, number][] {
  return props.points
    .map((p) => {
      const d = parseApiDateTime(p.recorded_at)
      const v = p[field]
      if (!d || v == null) return null
      return [d.getTime(), v] as [number, number]
    })
    .filter((x): x is [number, number] => x != null)
}

function onlineSeriesData(): [number, number][] {
  return props.points
    .map((p) => {
      const d = parseApiDateTime(p.recorded_at)
      if (!d) return null
      return [d.getTime(), p.online_count] as [number, number]
    })
    .filter((x): x is [number, number] => x != null)
}

function thresholdMarkLine(y: number, label: string, color: string): MarkLineComponentOption['data'] {
  return [
    {
      yAxis: y,
      name: label,
      lineStyle: { color, type: 'dashed', width: 1, opacity: 0.75 },
      label: {
        formatter: `${label} ${y}%`,
        color,
        fontSize: 10,
        position: 'insideEndTop',
      },
    },
  ]
}

function resourceMarkPoint(): MarkPointComponentOption {
  return {
    symbol: 'circle',
    symbolSize: 8,
    label: {
      fontSize: 10,
      formatter: (p) => {
        const raw = (p as { value?: unknown }).value
        const v = Array.isArray(raw) ? raw[1] : raw
        return typeof v === 'number' ? `${v}%` : ''
      },
    },
    data: [
      { type: 'max', name: '峰值' },
      { type: 'min', name: '谷值' },
    ],
  }
}

function formatAxisTime(ts: number, detailed = false): string {
  const bj = formatPointTimeBeijing(new Date(ts).toISOString())
  if (detailed) return bj
  return props.hours >= 168 ? bj.slice(5, 16) : bj.slice(5, 16)
}

function alertScatterData(): [number, number, number][] {
  return props.points
    .filter((p) => (p.alert_count ?? 0) > 0)
    .map((p) => {
      const ts = pointTimestampMs(p.recorded_at)
      const y = Math.max(p.cpu_avg ?? 0, p.mem_avg ?? 0, p.disk_avg ?? 0, 8)
      return [ts ?? 0, Math.min(y + 4, 99), p.alert_count ?? 0] as [number, number, number]
    })
    .filter((d) => d[0] > 0)
}

function buildAlertsHtml(p: FleetTrendPoint): string {
  const alerts = p.alerts ?? []
  if (!alerts.length) {
    return '<div style="margin-top:8px;font-size:11px;opacity:0.65">本 10 分钟无告警</div>'
  }
  const lines = alerts.slice(0, 8).map((a) => {
    const label = a.alert_type_label || a.alert_type
    const kind = a.is_recovery ? '恢复' : '告警'
    const color = a.is_recovery ? '#4CAF50' : '#F44336'
    const time = a.created_at ? formatPointTimeBeijing(a.created_at).slice(11, 19) : ''
    return `<div style="font-size:11px;line-height:1.45">
      <span style="color:${color};font-weight:600">${kind}</span>
      ${a.server_name || '—'} · ${label} ${a.value || ''}
      ${time ? `<span style="opacity:0.7"> @ ${time}</span>` : ''}
    </div>`
  })
  const more = alerts.length > 8 ? `<div style="font-size:11px;opacity:0.7">还有 ${alerts.length - 8} 条…</div>` : ''
  return `<div style="margin-top:8px;padding-top:8px;border-top:1px solid rgba(128,128,128,0.25)">
    <div style="font-weight:600;font-size:12px;margin-bottom:4px">告警 ${alerts.length} 条（10 分钟窗口）</div>
    ${lines.join('')}${more}
  </div>`
}

function buildTooltipHtml(p: FleetTrendPoint, idx: number): string {
  const time = formatPointTimeBeijing(p.recorded_at)
  const prev = idx > 0 ? props.points[idx - 1] : null
  const row = (dot: string, label: string, val: number | null, prevVal: number | null, unit: string) => {
    let delta = ''
    if (val != null && prevVal != null) {
      const d = Math.round((val - prevVal) * 10) / 10
      const color = d > 0 ? '#FB8C00' : d < 0 ? '#4CAF50' : '#9E9E9E'
      const sign = d > 0 ? '+' : ''
      delta = `<span style="color:${color};font-size:11px;margin-left:6px">${sign}${d}${unit}</span>`
    }
    const display = val != null ? `<b>${val}${unit}</b>` : '—'
    return `<tr>
      <td style="padding:2px 8px 2px 0;white-space:nowrap">
        <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${dot};margin-right:6px"></span>${label}
      </td>
      <td style="padding:2px 0;text-align:right">${display}${delta}</td>
    </tr>`
  }
  const coverage =
    p.sample_count != null && p.online_count > 0
      ? Math.round((p.sample_count / p.online_count) * 100)
      : null
  const meta = [
    `在线 <b>${p.online_count}</b> 台`,
    p.sample_count != null ? `采样 <b>${p.sample_count}</b>` : null,
    coverage != null ? `覆盖率 <b>${coverage}%</b>` : null,
  ]
    .filter(Boolean)
    .join(' · ')

  return `<div style="min-width:200px;line-height:1.5">
    <div style="font-weight:600;font-size:13px;margin-bottom:8px">${time}</div>
    <table style="width:100%;border-collapse:collapse;font-size:12px">${[
      row(themeColor('primary'), 'CPU', p.cpu_avg, prev?.cpu_avg ?? null, '%'),
      row(themeColor('warning'), '内存', p.mem_avg, prev?.mem_avg ?? null, '%'),
      row('#9C27B0', '磁盘', p.disk_avg, prev?.disk_avg ?? null, '%'),
      row(themeColor('success'), '在线', p.online_count, prev?.online_count ?? null, ' 台'),
    ].join('')}</table>
    <div style="margin-top:8px;font-size:11px;opacity:0.75">${meta}</div>
    ${buildAlertsHtml(p)}
  </div>`
}

const defaultZoomStart = computed(() => {
  if (props.zoomStart != null) return props.zoomStart
  return props.hours >= 168 ? 65 : 0
})

const defaultZoomEnd = computed(() => props.zoomEnd ?? 100)

const chartOption = computed((): ECOption => {
  const dark = theme.current.value.dark
  const text = dark ? 'rgba(255,255,255,0.78)' : 'rgba(0,0,0,0.65)'
  const axisLine = dark ? 'rgba(255,255,255,0.14)' : 'rgba(0,0,0,0.1)'
  const splitLine = dark ? 'rgba(255,255,255,0.07)' : 'rgba(0,0,0,0.06)'

  const primary = themeColor('primary')
  const warning = themeColor('warning')
  const purple = '#9C27B0'
  const success = themeColor('success')
  const error = themeColor('error')

  const alertScatter = alertScatterData()

  return {
    animationDuration: 600,
    animationEasing: 'cubicOut',
    color: [primary, warning, purple, success, error],
    grid: {
      left: 12,
      right: 16,
      top: 52,
      bottom: 78,
      containLabel: true,
    },
    legend: {
      top: 6,
      left: 'center',
      textStyle: { color: text, fontSize: 12 },
      itemWidth: 18,
      itemHeight: 8,
      itemGap: 20,
      selected: {
        在线台数: false,
        告警: alertScatter.length > 0,
      },
    },
    tooltip: {
      trigger: 'axis',
      confine: true,
      axisPointer: {
        type: 'cross',
        snap: true,
        crossStyle: { color: axisLine },
        lineStyle: { color: primary, type: 'dashed', width: 1 },
        label: {
          backgroundColor: dark ? '#333' : '#666',
          formatter: (params) => {
            const axisDimension = (params as { axisDimension?: string }).axisDimension
            const value = (params as { value?: number | string }).value
            if (axisDimension === 'x' && value != null) {
              return formatAxisTime(Number(value), true)
            }
            if (axisDimension === 'y' && value != null) {
              return `${Math.round(Number(value) * 10) / 10}`
            }
            return ''
          },
        },
      },
      backgroundColor: dark ? 'rgba(28,28,32,0.97)' : 'rgba(255,255,255,0.98)',
      borderColor: axisLine,
      borderWidth: 1,
      padding: [10, 12],
      extraCssText: 'box-shadow:0 8px 24px rgba(0,0,0,0.18);border-radius:8px;',
      textStyle: { color: dark ? '#eee' : '#222' },
      formatter(params) {
        const rows = Array.isArray(params) ? params : [params]
        const first = rows[0] as { axisValue?: number | string }
        const axisVal = first?.axisValue
        if (axisVal == null) return ''
        const ts = typeof axisVal === 'number' ? axisVal : Number(axisVal)
        const idx = findNearestPointIndex(props.points, ts)
        if (idx < 0 || idx >= props.points.length) return ''
        return buildTooltipHtml(props.points[idx], idx)
      },
    },
    toolbox: {
      right: 12,
      top: 4,
      itemSize: 14,
      iconStyle: { borderColor: text },
      feature: {
        dataZoom: { yAxisIndex: 'none', title: { zoom: '框选', back: '还原' } },
        restore: { title: '重置' },
        saveAsImage: { title: '导出图片', name: 'nexus-fleet-trend', pixelRatio: 2 },
      },
    },
    dataZoom: [
      {
        type: 'inside',
        start: defaultZoomStart.value,
        end: defaultZoomEnd.value,
        minValueSpan: INTERVAL_MS,
      },
      {
        type: 'slider',
        height: 24,
        bottom: 10,
        start: defaultZoomStart.value,
        end: defaultZoomEnd.value,
        borderColor: axisLine,
        backgroundColor: dark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.03)',
        fillerColor: dark ? 'rgba(25,118,210,0.2)' : 'rgba(25,118,210,0.15)',
        handleStyle: { color: primary, borderColor: primary },
        moveHandleStyle: { color: primary },
        textStyle: { color: text, fontSize: 10 },
        labelFormatter: (value: number) => formatAxisTime(value, true),
      },
    ],
    xAxis: {
      type: 'time',
      minInterval: INTERVAL_MS,
      maxInterval: props.hours >= 168 ? 6 * 60 * 60 * 1000 : 60 * 60 * 1000,
      axisLine: { lineStyle: { color: axisLine } },
      axisLabel: {
        color: text,
        fontSize: 10,
        hideOverlap: true,
        formatter: (value: number) => formatAxisTime(value, true),
      },
      axisTick: { show: false },
      splitLine: {
        show: true,
        lineStyle: { color: splitLine, type: 'dotted' },
      },
      minorSplitLine: { show: false },
    },
    yAxis: [
      {
        type: 'value',
        name: '利用率',
        min: 0,
        max: 100,
        nameTextStyle: { color: text, fontSize: 11, padding: [0, 0, 0, 4] },
        axisLabel: { color: text, fontSize: 10, formatter: '{value}%' },
        splitLine: { lineStyle: { color: splitLine, type: 'dashed' } },
      },
      {
        type: 'value',
        name: '在线台数',
        position: 'right',
        minInterval: 1,
        nameTextStyle: { color: text, fontSize: 11 },
        axisLabel: { color: text, fontSize: 10 },
        splitLine: { show: false },
      },
    ],
    series: [
      {
        name: 'CPU',
        type: 'line',
        smooth: 0.28,
        showSymbol: false,
        symbolSize: 7,
        connectNulls: false,
        emphasis: { focus: 'series', scale: true },
        lineStyle: { width: 2.5 },
        areaStyle: {
          color: {
            type: 'linear',
            x: 0,
            y: 0,
            x2: 0,
            y2: 1,
            colorStops: [
              { offset: 0, color: `${primary}40` },
              { offset: 1, color: `${primary}06` },
            ],
          },
        },
        markLine: {
          symbol: 'none',
          silent: true,
          data: thresholdMarkLine(props.thresholds.cpu, 'CPU 告警', error),
        },
        markPoint: resourceMarkPoint(),
        data: toSeriesData('cpu_avg'),
      },
      {
        name: '内存',
        type: 'line',
        smooth: 0.28,
        showSymbol: false,
        connectNulls: false,
        emphasis: { focus: 'series' },
        lineStyle: { width: 2 },
        areaStyle: {
          color: {
            type: 'linear',
            x: 0,
            y: 0,
            x2: 0,
            y2: 1,
            colorStops: [
              { offset: 0, color: `${warning}35` },
              { offset: 1, color: `${warning}05` },
            ],
          },
        },
        markLine: {
          symbol: 'none',
          silent: true,
          data: thresholdMarkLine(props.thresholds.mem, '内存告警', error),
        },
        data: toSeriesData('mem_avg'),
      },
      {
        name: '磁盘',
        type: 'line',
        smooth: 0.28,
        showSymbol: false,
        connectNulls: false,
        emphasis: { focus: 'series' },
        lineStyle: { width: 2 },
        markLine: {
          symbol: 'none',
          silent: true,
          data: thresholdMarkLine(props.thresholds.disk, '磁盘告警', error),
        },
        data: toSeriesData('disk_avg'),
      },
      {
        name: '在线台数',
        type: 'line',
        yAxisIndex: 1,
        smooth: 0.28,
        showSymbol: false,
        connectNulls: false,
        emphasis: { focus: 'series' },
        lineStyle: { width: 2, type: 'dashed', opacity: 0.85 },
        data: onlineSeriesData(),
      },
      ...(alertScatter.length
        ? [
            {
              name: '告警',
              type: 'scatter' as const,
              yAxisIndex: 0,
              symbol: 'pin',
              symbolSize: (data: unknown) => {
                const raw = data as [number, number, number] | undefined
                const count = raw?.[2] ?? 1
                return Math.min(28, 12 + Number(count) * 3)
              },
              itemStyle: { color: error },
              emphasis: { scale: 1.2 },
              z: 12,
              data: alertScatter,
            },
          ]
        : []),
    ],
  }
})

function onDataZoom(evt: { batch?: { start?: number; end?: number }[]; start?: number; end?: number }) {
  const batch = evt.batch?.[0]
  const startPercent = batch?.start ?? evt.start ?? 0
  const endPercent = batch?.end ?? evt.end ?? 100
  emit('range-change', { startPercent, endPercent })
}
</script>

<style scoped>
.fleet-echart {
  width: 100%;
}
</style>
