<script lang="ts">
const deviceTabPanes = Object.keys(DeviceType).map((key) => {
  const name = DeviceType[key as DeviceTypeKey];
  return {
    name,
    tab: DeviceTypeName[name],
  };
});
</script>

<script setup lang="ts">
import type { LineDevices, NdmDeviceResultVO, NdmNvrResultVO, Station } from '@/apis';
import { isNvrCluster } from '@/helper';
import { DeviceType, DeviceTypeName, tryGetDeviceTypeVal, type DeviceTypeKey, type DeviceTypeVal } from '@/enums';
import { destr } from 'destr';
import {
  NButton,
  NFlex,
  NInput,
  NRadio,
  NRadioGroup,
  NTab,
  NTabs,
  NTag,
  NTree,
  useThemeVars,
  type TagProps,
  type TreeInst,
  type TreeOption,
  type TreeOverrideNodeClickBehavior,
  type TreeProps,
} from 'naive-ui';
import { computed, h, ref, toRefs, useTemplateRef, watch, type CSSProperties } from 'vue';

const themeVars = useThemeVars();

const props = defineProps<{
  stationList: Station[];
  lineDevices: LineDevices;
  selectedStationCode?: string;
  selectedDeviceType: DeviceTypeVal;
  selectedDevice?: NdmDeviceResultVO;
}>();

const emit = defineEmits<{
  'select-device': [device: NdmDeviceResultVO, stationCode: string];
}>();

const { stationList, lineDevices, selectedStationCode, selectedDeviceType, selectedDevice } = toRefs(props);

const activeTab = ref<DeviceTypeVal>(DeviceType.Camera);
watch(
  () => selectedDeviceType.value,
  (newType) => (activeTab.value = newType),
  { immediate: true },
);
const selectedKeys = computed(() => (selectedDevice.value?.id ? [selectedDevice.value.id] : undefined));

// 树节点交互
const onSelectDevice = (device: NdmDeviceResultVO, stationCode: string) => {
  emit('select-device', device, stationCode);
};
const override: TreeOverrideNodeClickBehavior = ({ option }) => {
  const hasChildren = (option.children?.length ?? 0) > 0;
  const isDeviceNode = !!option['device'];
  if (hasChildren || !isDeviceNode) {
    return 'toggleExpand';
  } else {
    return 'none';
  }
};
const nodeProps: TreeProps['nodeProps'] = ({ option }) => {
  return {
    onDblclick: (payload: MouseEvent) => {
      if (option['device']) {
        payload.stopPropagation();
        const device = option['device'] as NdmDeviceResultVO;
        const stationCode = option['stationCode'] as string;
        onSelectDevice(device, stationCode);
      }
    },
  };
};

// ========== 设备树数据 ==========
const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
  const treeData: Record<string, TreeOption[]> = {};
  deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
    treeData[paneName] = stationList.value.map<TreeOption>((station) => {
      const { name: stationName, code: stationCode } = station;
      const devices = lineDevices.value[stationCode]?.[paneName] ?? ([] as NdmDeviceResultVO[]);
      const onlineDevices = devices?.filter((device) => device.deviceStatus === '10');
      const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
      // 对于录像机，需要根据clusterList字段以分号分隔设备IP，进一步形成子树结构
      if (paneName === DeviceType.Nvr) {
        const nvrs = devices as NdmNvrResultVO[];
        const nvrClusters: NdmNvrResultVO[] = [];
        const nvrSingletons: NdmNvrResultVO[] = [];
        for (const device of nvrs) {
          if (isNvrCluster(device)) {
            nvrClusters.push(device);
          } else {
            nvrSingletons.push(device);
          }
        }
        return {
          label: stationName,
          key: stationCode,
          prefix: () => renderStationNodePrefix(station),
          suffix: () => renderStationNodeSuffix(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
          children: nvrClusters.map<TreeOption>((nvrCluster) => {
            return {
              label: `${nvrCluster.name}`,
              key: nvrCluster.id,
              prefix: () => renderDeviceNodePrefix(nvrCluster, stationCode),
              suffix: () => `${nvrCluster.ipAddress}`,
              children: nvrSingletons.map<TreeOption>((nvr) => {
                return {
                  label: `${nvr.name}`,
                  key: nvr.id,
                  prefix: () => renderDeviceNodePrefix(nvr, stationCode),
                  suffix: () => `${nvr.ipAddress}`,
                  // 当选择设备时，能获取到设备的所有信息，以及设备所属的车站
                  device: nvr,
                  stationCode,
                };
              }),
              // 当选择设备时，能获取到设备的所有信息，以及设备所属的车站
              device: nvrCluster,
              stationCode,
            };
          }),
        };
      }
      return {
        label: stationName,
        key: stationCode,
        prefix: () => renderStationNodePrefix(station),
        suffix: () => renderStationNodeSuffix(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
        children:
          lineDevices.value[stationCode]?.[paneName]?.map<TreeOption>((dev) => {
            const device = dev as NdmDeviceResultVO;
            return {
              label: `${device.name}`,
              key: device.id,
              prefix: () => renderDeviceNodePrefix(device, stationCode),
              suffix: () => `${device.ipAddress}`,
              // 当选择设备时，能获取到设备的所有信息，以及设备所属的车站
              device,
              stationCode,
            };
          }) ?? [],
      };
    });
  });
  return treeData;
});
const renderStationNodePrefix = (station: Station) => {
  const { online } = station;
  const tagType: TagProps['type'] = online ? 'success' : 'error';
  const tagText = online ? '在线' : '离线';
  return h(NTag, { type: tagType, size: 'tiny' }, () => tagText);
};
const renderStationNodeSuffix = (onlineCount: number, offlineCount: number, count: number) => {
  return h('span', null, [
    '(',
    h('span', { style: { color: themeVars.value.successColor } }, `${onlineCount}`),
    '/',
    h('span', { style: { color: themeVars.value.errorColor } }, `${offlineCount}`),
    '/',
    `${count}`,
    ')',
  ]);
};
const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: string) => {
  const renderDeviceStatusTag = (device: NdmDeviceResultVO) => {
    const { deviceStatus } = device;
    // const tagType: TagProps['type'] = deviceStatus === '10' ? 'success' : deviceStatus === '20' ? 'error' : 'warning';
    // const tagText = device.deviceStatus === '10' ? '在线' : device.deviceStatus === '20' ? '离线' : '未知';
    // return h(NTag, { type: tagType, bordered: false, size: 'tiny' }, { default: () => '◉' }); // ⁕∘∙⋅﹒▪●
    const color = deviceStatus === '10' ? themeVars.value.successColor : deviceStatus === '20' ? themeVars.value.errorColor : themeVars.value.warningColor;
    return h('div', { style: { color } }, { default: () => '◉' }); // ⁕∘∙⋅﹒▪●
  };
  const renderViewDeviceButton = (device: NdmDeviceResultVO, stationCode: string) => {
    return h(
      NButton,
      {
        text: true,
        size: 'tiny',
        type: 'info',
        style: {
          marginRight: 8,
        } as CSSProperties,
        onClick: (e: MouseEvent) => {
          e.stopPropagation();
          // 选择设备
          onSelectDevice(device, stationCode);
        },
      },
      () => '查看',
    );
  };
  return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
};

// ========== 设备树搜索 ==========
const searchInput = ref('');
const statusInput = ref('');
// 设备树将搜索框和单选框的值都交给NTree的pattern属性
// 但是如果一个车站下没有匹配的设备，那么这个车站节点也不会显示
const searchPattern = computed(() => {
  const search = searchInput.value;
  const status = statusInput.value;
  if (!search && !status) return ''; // 如果pattern非空会导致NTree组件认为筛选完成，UI上发生全量匹配
  return JSON.stringify({ search: searchInput.value, status: statusInput.value });
});
const searchFilter = (pattern: string, node: TreeOption): boolean => {
  const { search, status } = destr<{ search: string; status: string }>(pattern);
  const device = node['device'] as NdmDeviceResultVO | undefined;
  const { name, ipAddress, deviceId, deviceStatus } = device ?? {};
  const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search) || (deviceId ?? '').includes(search);
  const statusMatched = status === '' || status === deviceStatus;
  return searchMatched && statusMatched;
};

// ========== 设备树交互 ==========
const expandedKeys = ref<string[]>();
const deviceTreeInst = useTemplateRef<TreeInst>('deviceTreeInst');
const onClickFoldDeviceTree = () => {
  expandedKeys.value = [];
};
const onClickLocateDeviceTree = () => {
  const stationCode = selectedStationCode.value;
  const device = selectedDevice.value;
  if (!stationCode || !device?.id) return;
  const deviceTypeVal = tryGetDeviceTypeVal(device.deviceType);
  if (!!deviceTypeVal) {
    activeTab.value = deviceTypeVal;
  }

  const expanded = [stationCode];
  if (activeTab.value === DeviceType.Nvr) {
    const nvrs = lineDevices.value[stationCode]?.[DeviceType.Nvr];
    if (nvrs) {
      const clusterKeys = nvrs.filter((nvr) => !!nvr.clusterList?.trim() && nvr.clusterList !== nvr.ipAddress).map((nvr) => String(nvr.id));
      expanded.push(...clusterKeys);
    }
  }
  expandedKeys.value = expanded;

  // 由于数据量大所以开启虚拟滚动，
  // 但是无法知晓NTree内部的虚拟列表容器何时创建完成，所以使用setTimeout延迟固定时间后执行滚动
  scrollDeviceTreeToSelectedDevice();
};
const scrollDeviceTreeToSelectedDevice = () => {
  setTimeout(() => {
    const inst = deviceTreeInst.value;
    inst?.scrollTo({ key: selectedDevice?.value?.id, behavior: 'smooth' });
  }, 350);
};
</script>

<template>
  <div style="height: 100%; display: flex; flex-direction: column">
    <div style="padding: 12px; flex-shrink: 0">
      <NInput v-model:value="searchInput" placeholder="搜索设备名称、设备ID或IP地址" clearable />
      <NFlex justify="space-between" align="center">
        <NRadioGroup v-model:value="statusInput">
          <NRadio value="">全部</NRadio>
          <NRadio value="10">在线</NRadio>
          <NRadio value="20">离线</NRadio>
        </NRadioGroup>
        <NFlex :align="'center'">
          <NButton text size="tiny" type="info" @click="onClickFoldDeviceTree">收起</NButton>
          <NButton text size="tiny" type="info" @click="onClickLocateDeviceTree">定位</NButton>
        </NFlex>
      </NFlex>
    </div>

    <div style="min-height: 0; overflow: hidden; flex: 1; display: flex">
      <div style="height: 100%; flex: 0 0 auto">
        <NTabs v-model:value="activeTab" animated type="line" placement="left" style="height: 100%">
          <NTab v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab"></NTab>
        </NTabs>
      </div>
      <div style="min-width: 0; flex: 1 1 auto">
        <NTree
          :ref="'deviceTreeInst'"
          v-model:expanded-keys="expandedKeys"
          :selected-keys="selectedKeys"
          :data="activeTab ? lineDeviceTreeData[activeTab] : []"
          :show-irrelevant-nodes="false"
          :pattern="searchPattern"
          :filter="searchFilter"
          :override-default-node-click-behavior="override"
          :node-props="nodeProps"
          :default-expand-all="false"
          block-line
          block-node
          show-line
          virtual-scroll
          style="height: 100%"
        />
      </div>
    </div>
  </div>
</template>

<style scoped></style>
