<template>
  <div class="p-2 real-time-curve-page">
    <el-row :gutter="12">
      <el-col :xs="24" :md="7" :lg="6">
        <el-card shadow="hover" class="side-card">
          <el-form label-position="top">
            <el-form-item label="设备">
              <el-input v-model="treeKeyword" clearable placeholder="搜索设备" prefix-icon="Search" />
            </el-form-item>
          </el-form>
          <el-tree
            ref="deviceTreeRef"
            class="device-tree"
            :data="deviceTree"
            node-key="nodeKey"
            show-checkbox
            check-strictly
            default-expand-all
            :filter-node-method="filterTreeNode"
            :props="{ label: 'name', children: 'children', disabled: 'disabled' }"
            @check="handleDeviceCheck"
          >
            <template #default="{ data }">
              <span class="device-node" :class="{ muted: data.disabled }">
                <span>{{ data.name }}</span>
                <el-tag v-if="data.isDevice === '1'" size="small" type="success">设备</el-tag>
              </span>
            </template>
          </el-tree>
        </el-card>
      </el-col>

      <el-col :xs="24" :md="17" :lg="18">
        <el-card shadow="hover" class="filter-card">
          <el-form :inline="true">
            <el-form-item label="能源指标">
              <el-select
                v-model="selectedMetricIds"
                multiple
                collapse-tags
                collapse-tags-tooltip
                filterable
                clearable
                placeholder="选择能源指标"
                class="metric-select"
              >
                <el-option v-for="metric in filteredMetricOptions" :key="metric.id" :label="formatMetricLabel(metric)" :value="String(metric.id)" />
              </el-select>
            </el-form-item>
            <el-form-item label="时间间隔">
              <el-select v-model="pollInterval" class="interval-select">
                <el-option label="5秒" :value="5" />
                <el-option label="10秒" :value="10" />
                <el-option label="30秒" :value="30" />
                <el-option label="60秒" :value="60" />
              </el-select>
            </el-form-item>
            <el-form-item label="状态">
              <el-tag :type="running ? 'success' : 'info'" effect="plain">{{ running ? '轮询中' : '待启动' }}</el-tag>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" :loading="loading" @click="startPolling">查询</el-button>
              <el-button icon="VideoPause" :disabled="!running" @click="stopPolling">停止</el-button>
              <el-button icon="Refresh" @click="resetAll">重置</el-button>
            </el-form-item>
          </el-form>
          <div class="status-line">
            <span>已选设备：{{ selectedDeviceIds.length }}</span>
            <span>累计数据点：{{ totalPointCount }}</span>
            <span>最近窗口：{{ lastWindowLabel }}</span>
          </div>
        </el-card>

        <template v-if="selectedMetrics.length">
          <el-card v-for="metric in selectedMetrics" :key="String(metric.id)" shadow="hover" class="chart-card">
            <template #header>
              <div class="chart-header">
                <span>{{ metric.metricName || metric.collectionField }}</span>
                <el-tag effect="plain">{{ metricPointCount(String(metric.id)) }} 点</el-tag>
              </div>
            </template>
            <div v-loading="loading" class="chart-wrap">
              <div :ref="(el) => setChartRef(String(metric.id), el)" class="chart"></div>
              <el-empty v-if="!metricPointCount(String(metric.id))" class="chart-empty" description="等待实时数据" />
            </div>
          </el-card>
        </template>
        <el-card v-else shadow="hover" class="chart-card">
          <el-empty class="stage-empty" description="请选择能源指标并点击查询" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script setup name="RealTimeCurve" lang="ts">
import * as echarts from 'echarts';
import { ElMessage } from 'element-plus';
import { getRealTimeCurve } from '@/api/report';
import { RealTimeCurvePoint } from '@/api/report/types';
import { listEnergyMetric } from '@/api/ems/energyMetric';
import { EnergyMetricVO } from '@/api/ems/energyMetric/types';
import { listFloorInfo } from '@/api/system/floor';
import { FloorInfoVO } from '@/api/system/floor/types';

interface DeviceTreeNode {
  id: string | number;
  nodeKey: string;
  name: string;
  isDevice?: string;
  deviceId?: string;
  deviceType?: string | number;
  disabled?: boolean;
  children?: DeviceTreeNode[];
}

interface MetricState {
  xAxis: string[];
  series: Record<string, Array<number | null>>;
  points: number;
}

const deviceTreeRef = ref<ElTreeInstance>();
const deviceTree = ref<DeviceTreeNode[]>([]);
const treeKeyword = ref('');
const selectedDeviceIds = ref<string[]>([]);
const selectedDeviceTypes = ref<string[]>([]);
const metricOptions = ref<EnergyMetricVO[]>([]);
const selectedMetricIds = ref<string[]>([]);
const pollInterval = ref(10);
const loading = ref(false);
const running = ref(false);
const lastBeginTime = ref('');
const lastEndTime = ref('');
const metricStates = reactive<Record<string, MetricState>>({});
const charts = new Map<string, echarts.ECharts>();
const chartEls = new Map<string, HTMLElement>();
let timer: number | undefined;

const filteredMetricOptions = computed(() => {
  if (!selectedDeviceTypes.value.length) return metricOptions.value;
  const types = new Set(selectedDeviceTypes.value);
  return metricOptions.value.filter((metric) => types.has(String(metric.type)));
});

const selectedMetrics = computed(() => {
  const selected = new Set(selectedMetricIds.value.map(String));
  return metricOptions.value.filter((metric) => selected.has(String(metric.id)));
});

const totalPointCount = computed(() => Object.values(metricStates).reduce((sum, state) => sum + state.points, 0));
const lastWindowLabel = computed(() => (lastBeginTime.value && lastEndTime.value ? `${lastBeginTime.value} -> ${lastEndTime.value}` : '-'));

watch(treeKeyword, (value) => {
  deviceTreeRef.value?.filter(value);
});

watch(selectedDeviceTypes, () => {
  const allowedIds = new Set(filteredMetricOptions.value.map((metric) => String(metric.id)));
  selectedMetricIds.value = selectedMetricIds.value.filter((id) => allowedIds.has(String(id)));
});

function pad(value: number) {
  return String(value).padStart(2, '0');
}

function formatDateTime(date: Date) {
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}

function formatMetricLabel(metric: EnergyMetricVO) {
  return `${metric.metricName || metric.collectionField || ''}${metric.unit ? `(${metric.unit})` : ''}`;
}

function normalizeDeviceTree(rows: FloorInfoVO[]): DeviceTreeNode[] {
  return rows
    .map((row) => {
      const children = normalizeDeviceTree(row.children || []);
      if (row.isDevice === '1' && row.deviceId) {
        return {
          id: row.id,
          nodeKey: `device_${row.id}_${row.deviceId}`,
          name: row.name || row.deviceId,
          isDevice: row.isDevice,
          deviceId: row.deviceId,
          deviceType: row.deviceType,
          disabled: false,
          children: []
        };
      }
      if (!children.length) return null;
      return {
        id: row.id,
        nodeKey: `node_${row.id}`,
        name: row.name,
        disabled: true,
        children
      };
    })
    .filter(Boolean) as DeviceTreeNode[];
}

function filterTreeNode(value: string, data: DeviceTreeNode) {
  if (!value) return true;
  return String(data.name || '').includes(value) || String(data.deviceId || '').includes(value);
}

function collectCheckedDevices() {
  const checked = (deviceTreeRef.value?.getCheckedNodes?.() || []) as DeviceTreeNode[];
  const devices = checked.filter((node) => node.isDevice === '1' && node.deviceId);
  selectedDeviceIds.value = [...new Set(devices.map((node) => String(node.deviceId)))];
  selectedDeviceTypes.value = [...new Set(devices.map((node) => String(node.deviceType || '')).filter(Boolean))];
}

function handleDeviceCheck() {
  collectCheckedDevices();
}

function metricPointCount(metricId: string) {
  return metricStates[metricId]?.points || 0;
}

function setChartRef(metricId: string, el: Element | ComponentPublicInstance | null) {
  if (!(el instanceof HTMLElement)) return;
  chartEls.set(metricId, el);
  if (!charts.has(metricId)) {
    charts.set(metricId, echarts.init(el, 'macarons'));
  }
  renderMetric(metricId);
}

function ensureMetricState(metricId: string) {
  if (!metricStates[metricId]) {
    metricStates[metricId] = {
      xAxis: [],
      series: {},
      points: 0
    };
  }
  selectedDeviceIds.value.forEach((deviceId) => {
    if (!metricStates[metricId].series[deviceId]) {
      metricStates[metricId].series[deviceId] = Array(metricStates[metricId].xAxis.length).fill(null);
    }
  });
  return metricStates[metricId];
}

function resetMetricStates() {
  Object.keys(metricStates).forEach((key) => delete metricStates[key]);
  selectedMetricIds.value.forEach((metricId) => ensureMetricState(String(metricId)));
}

function renderMetric(metricId: string) {
  const chart = charts.get(metricId);
  const metric = selectedMetrics.value.find((item) => String(item.id) === String(metricId));
  if (!chart || !metric) return;
  const state = ensureMetricState(metricId);
  const unit = metric.unit || '';
  chart.setOption(
    {
      color: ['#00A870', '#1668DC', '#F59E0B', '#D83B01', '#7C3AED', '#0891B2', '#64748B'],
      tooltip: {
        trigger: 'axis',
        valueFormatter: (value: unknown) => `${Number(value || 0).toFixed(3)}${unit ? ` ${unit}` : ''}`
      },
      legend: { type: 'scroll', top: 0, right: 8 },
      grid: { top: 42, left: 54, right: 24, bottom: 38 },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: state.xAxis,
        axisLabel: { color: '#607085' }
      },
      yAxis: {
        type: 'value',
        name: unit,
        nameTextStyle: { color: '#607085' },
        splitLine: { lineStyle: { color: 'rgba(96,112,133,.18)' } }
      },
      series: selectedDeviceIds.value.map((deviceId) => ({
        deviceId,
        name: deviceId,
        type: 'line',
        showSymbol: false,
        smooth: true,
        connectNulls: true,
        data: [...(state.series[deviceId] || [])]
      }))
    },
    true
  );
  chart.resize();
}

function renderAllCharts() {
  selectedMetricIds.value.forEach((metricId) => renderMetric(String(metricId)));
}

async function appendPoints(points: RealTimeCurvePoint[]) {
  if (!points.length) return;
  const pointsByMetric = new Map<string, RealTimeCurvePoint[]>();
  points.forEach((point) => {
    const key = String(point.metricId);
    if (!pointsByMetric.has(key)) pointsByMetric.set(key, []);
    pointsByMetric.get(key)?.push(point);
  });

  for (const [metricId, items] of pointsByMetric.entries()) {
    const state = ensureMetricState(metricId);
    const valueMap = new Map<string, Map<string, number>>();
    items.forEach((point) => {
      if (!valueMap.has(point.reportTime)) valueMap.set(point.reportTime, new Map());
      valueMap.get(point.reportTime)?.set(String(point.deviceId), Number(point.value));
    });
    const newTimes = [...valueMap.keys()].filter((time) => !state.xAxis.includes(time)).sort();
    if (!newTimes.length) {
      items.forEach((point) => {
        const timeIndex = state.xAxis.indexOf(point.reportTime);
        const data = state.series[String(point.deviceId)];
        if (timeIndex >= 0 && data) data[timeIndex] = Number(point.value);
      });
      await nextTick();
      renderMetric(metricId);
      continue;
    }
    newTimes.forEach((time) => {
      state.xAxis.push(time);
      selectedDeviceIds.value.forEach((deviceId) => {
        if (!state.series[deviceId]) state.series[deviceId] = Array(state.xAxis.length - 1).fill(null);
        state.series[deviceId].push(valueMap.get(time)?.get(deviceId) ?? null);
      });
    });
    state.points += items.length;
    await nextTick();
    renderMetric(metricId);
  }
}

async function fetchIncrement(beginTime: string, endTime: string) {
  const res = await getRealTimeCurve({
    deviceIds: selectedDeviceIds.value.join(','),
    metricIds: selectedMetricIds.value.join(','),
    beginTime,
    endTime
  });
  await appendPoints(res.data.points || []);
  lastBeginTime.value = beginTime;
  lastEndTime.value = endTime;
}

async function pollOnce() {
  if (!running.value) return;
  const begin = lastEndTime.value || formatDateTime(new Date());
  const end = formatDateTime(new Date());
  try {
    await fetchIncrement(begin, end);
  } catch (error: any) {
    stopPolling();
    ElMessage.error(error?.message || '实时曲线查询失败');
  }
}

async function startPolling() {
  collectCheckedDevices();
  if (!selectedDeviceIds.value.length) {
    ElMessage.warning('请选择设备');
    return;
  }
  if (!selectedMetricIds.value.length) {
    ElMessage.warning('请选择能源指标');
    return;
  }
  stopPolling(false);
  loading.value = true;
  try {
    resetMetricStates();
    await nextTick();
    renderAllCharts();
    const now = formatDateTime(new Date());
    lastBeginTime.value = now;
    lastEndTime.value = now;
    await fetchIncrement(now, now);
    running.value = true;
    timer = window.setInterval(pollOnce, pollInterval.value * 1000);
  } finally {
    loading.value = false;
  }
}

function stopPolling(showMessage = true) {
  if (timer) {
    window.clearInterval(timer);
    timer = undefined;
  }
  const wasRunning = running.value;
  running.value = false;
  if (showMessage && wasRunning) ElMessage.info('已停止实时轮询');
}

function resetAll() {
  stopPolling(false);
  selectedMetricIds.value = [];
  lastBeginTime.value = '';
  lastEndTime.value = '';
  Object.keys(metricStates).forEach((key) => delete metricStates[key]);
  charts.forEach((chart) => chart.clear());
}

function resizeCharts() {
  charts.forEach((chart) => chart.resize());
}

async function loadBaseData() {
  const [treeRes, metricRes] = await Promise.all([
    listFloorInfo(),
    listEnergyMetric({ pageNum: 1, pageSize: 1000 })
  ]);
  deviceTree.value = normalizeDeviceTree((treeRes as any).rows || []);
  metricOptions.value = (metricRes as any).rows || [];
}

onMounted(async () => {
  await loadBaseData();
  window.addEventListener('resize', resizeCharts);
});

onBeforeUnmount(() => {
  stopPolling(false);
  window.removeEventListener('resize', resizeCharts);
  charts.forEach((chart) => chart.dispose());
  charts.clear();
  chartEls.clear();
});
</script>

<style scoped>
.side-card,
.filter-card,
.chart-card {
  margin-bottom: 12px;
}

.device-tree {
  max-height: 640px;
  overflow: auto;
}

.device-node {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.device-node.muted {
  color: var(--el-text-color-secondary);
}

.metric-select {
  width: 260px;
}

.interval-select {
  width: 110px;
}

.status-line {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  overflow: hidden;
  color: var(--el-text-color-secondary);
  font-size: 12px;
  text-overflow: ellipsis;
}

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

.chart-wrap {
  position: relative;
  min-height: 360px;
}

.chart {
  width: 100%;
  height: 360px;
}

.chart-empty {
  position: absolute;
  inset: 80px 0 0;
  pointer-events: none;
}

.stage-empty {
  min-height: 560px;
}

@media (max-width: 760px) {
  .metric-select {
    width: 100%;
  }
}
</style>
