<script setup lang="ts">
import { detailDeviceApi, updateDeviceApi, type LinkDescription, type NdmDeviceResultVO, type NdmSwitchLinkDescription, type NdmSwitchPortInfo, type NdmSwitchResultVO, type Station } from '@/apis';
import { SwitchPortLinkModal } from '@/components';
import { usePermission } from '@/composables';
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
import { PERMISSION_TYPE_LITERALS } from '@/enums';
import { getPortStatusValue, transformPortSpeed } from '@/helpers';
import { useDeviceStore, useSettingStore } from '@/stores';
import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import { isCancel } from 'axios';
import destr from 'destr';
import { cloneDeep, isFunction } from 'es-toolkit';
import { NCard, NDescriptions, NDescriptionsItem, NDropdown, NPopover, useThemeVars, type DropdownOption } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { computed, inject, onBeforeUnmount, ref, toRefs } from 'vue';

const props = defineProps<{
  ndmDevice: NdmSwitchResultVO;
  station: Station;
  ports?: NdmSwitchPortInfo[];
}>();

const themeVars = useThemeVars();

const deviceStore = useDeviceStore();
const { lineDevices } = storeToRefs(deviceStore);

const settingStore = useSettingStore();
const { useLocalDB } = storeToRefs(settingStore);

const { hasPermission } = usePermission();

const { ndmDevice, station, ports } = toRefs(props);

const showCard = computed(() => !!ports.value);

const switchSlots = computed(() => {
  // 解析端口名称，将端口按槽位进行分组
  const groupMap = new Map<string, NdmSwitchPortInfo[]>();
  ports.value?.forEach((port) => {
    const parts = port.portName.split('/');
    if (parts.length >= 3) {
      const slotName = `${parts[0]}/${parts[1]}`;
      if (!groupMap.has(slotName)) {
        groupMap.set(slotName, []);
      }
      groupMap.get(slotName)!.push(port);
    }
  });

  // 将Map转换为entries
  const entries = Array.from(groupMap.entries());

  // 按槽位进行排序
  const sorted = entries.sort(([aSlotName], [bSlotName]) => {
    const [mainA = 0, subA = 0] = aSlotName.split('/').map((value) => parseInt(value));
    const [mainB = 0, subB = 0] = bSlotName.split('/').map((value) => parseInt(value));
    return mainA - mainB || subA - subB;
  });

  // 按端口号进行排序
  return sorted.map(([slotName, ports]) => ({
    slotName,
    ports: ports.sort((portA, portB) => {
      const portNumA = parseInt(portA.portName.split('/').at(2) ?? '0');
      const portNumB = parseInt(portB.portName.split('/').at(2) ?? '0');
      return portNumA - portNumB;
    }),
  }));
});

const getPortClassName = (port: NdmSwitchPortInfo) => {
  if (port.upDown === 1) {
    return 'port-up';
  }
  if (port.upDown === 2) {
    return 'port-down';
  }
  return 'port-unknown';
};

const upperDeviceLinkDescription = computed(() => {
  const result = destr<any>(ndmDevice.value.linkDescription);
  if (!result) return null;
  if (typeof result !== 'object') return null;
  return result as NdmSwitchLinkDescription;
});

const getLowerDeviceByPort = (port: NdmSwitchPortInfo) => {
  if (!upperDeviceLinkDescription.value) return null;
  const downstream = upperDeviceLinkDescription.value.downstream;
  if (!downstream) return null;
  const deviceStoreIndex = downstream[port.portName];
  if (!deviceStoreIndex) return null;
  const { stationCode, deviceType, deviceDbId } = deviceStoreIndex;
  const stationDevices = lineDevices.value[stationCode];
  if (!stationDevices) return null;
  const devices = stationDevices[deviceType];
  const lowerDevice = devices.find((device) => device.id === deviceDbId);
  if (!lowerDevice) {
    // 下游设备不存在时解除关联
    const modifiedUpperDevice = cloneDeep(ndmDevice.value);
    const modifiedUpperLinkDescription = cloneDeep(upperDeviceLinkDescription.value);
    delete modifiedUpperLinkDescription.downstream?.[port.portName];
    modifiedUpperDevice.linkDescription = JSON.stringify(modifiedUpperLinkDescription);
    // 不需要等待异步
    const stationCode = station.value.code;
    updateDeviceApi(modifiedUpperDevice, { stationCode }).then(() => {
      detailDeviceApi(modifiedUpperDevice, { stationCode }).then((upperDevice) => {
        if (!upperDevice) return;
        deviceStore.patchDevice(stationCode, { ...upperDevice });
      });
    });
    return null;
  }
  return lowerDevice;
};

// 获取从父组件注入的selectDevice函数
const selectDeviceFn = inject(SELECT_DEVICE_FN_INJECTION_KEY);
// 跳转到下游设备
const navigateToLowerDevice = (port: NdmSwitchPortInfo) => {
  const lowerDevice = getLowerDeviceByPort(port);
  if (!lowerDevice) return;
  if (!!selectDeviceFn && !!selectDeviceFn.value) {
    selectDeviceFn.value(lowerDevice, station.value.code);
  }
};

const showModal = ref(false);
const contextmenu = ref<{ x: number; y: number; port?: NdmSwitchPortInfo }>({ x: 0, y: 0 });
const showContextmenu = ref(false);
const contextmenuOptions = computed<DropdownOption[]>(() => [
  {
    label: '关联设备',
    key: 'link-device',
    onSelect: () => {
      showContextmenu.value = false;
      showModal.value = true;
    },
  },
  {
    label: '解除关联',
    key: 'unlink-device',
    onSelect: () => {
      showContextmenu.value = false;
      const port = contextmenu.value.port;
      if (!port) return;
      const lowerDevice = getLowerDeviceByPort(port);
      if (!lowerDevice) return;
      window.$dialog.warning({
        title: '确认解除关联吗？',
        content: `将解除【${port.portName}】与【${lowerDevice.name}】的关联关系。`,
        style: { width: '600px' },
        contentStyle: { height: '60px' },
        negativeText: '取消',
        positiveText: '确认',
        onNegativeClick: () => {
          window.$dialog.destroyAll();
        },
        onPositiveClick: () => {
          window.$dialog.destroyAll();
          unlinkDevice({ port, lowerDevice });
        },
      });
    },
  },
]);
const onSelectDropdownOption = (key: string, option: DropdownOption) => {
  const onSelect = option['onSelect'];
  if (isFunction(onSelect)) {
    onSelect();
  }
};
const onContextmenu = (payload: PointerEvent, port: NdmSwitchPortInfo) => {
  payload.stopPropagation();
  payload.preventDefault();
  if (!hasPermission(station.value.code, PERMISSION_TYPE_LITERALS.OPERATION)) return;
  const { clientX, clientY } = payload;
  contextmenu.value = { x: clientX, y: clientY, port };
  showContextmenu.value = true;
};

const abortController = ref<AbortController>(new AbortController());
const { mutate: unlinkDevice } = useMutation({
  mutationFn: async (params: { port: NdmSwitchPortInfo; lowerDevice: NdmDeviceResultVO }) => {
    abortController.value.abort();
    abortController.value = new AbortController();

    window.$loadingBar.start();

    const { port, lowerDevice } = params;

    if (!upperDeviceLinkDescription.value) return;

    // 1. 从下游设备的linkDescription的upstream字段中删除当前上游设备
    const modifiedLowerDevice = cloneDeep(lowerDevice);
    // 解除关联时下游设备的linkDescription一定存在
    const modifiedLowerDeviceLinkDescription = destr<LinkDescription>(modifiedLowerDevice.linkDescription);
    // upstream字段存在时才进行删除操作
    if (modifiedLowerDeviceLinkDescription.upstream) {
      const index = modifiedLowerDeviceLinkDescription.upstream.findIndex((deviceStoreIndex) => deviceStoreIndex.deviceDbId === ndmDevice.value.id);
      if (index !== -1) {
        modifiedLowerDeviceLinkDescription.upstream.splice(index, 1);
      }
    }
    modifiedLowerDevice.linkDescription = JSON.stringify(modifiedLowerDeviceLinkDescription);

    // 2. 修改上游设备的linkDescription的downstream字段
    const modifiedUpperDevice = cloneDeep(ndmDevice.value);
    const modifiedUpperLinkDescription = cloneDeep(upperDeviceLinkDescription.value);
    delete modifiedUpperLinkDescription.downstream?.[port.portName];
    modifiedUpperDevice.linkDescription = JSON.stringify(modifiedUpperLinkDescription);

    // 3. 发起update请求并获取最新的设备详情（使用本地数据库时直接修改本地数据）
    if (useLocalDB.value) {
      return { upperDevice: modifiedUpperDevice, lowerDevice: modifiedLowerDevice };
    }
    const stationCode = station.value.code;
    const signal = abortController.value.signal;
    await updateDeviceApi(modifiedUpperDevice, { stationCode, signal });
    await updateDeviceApi(modifiedLowerDevice, { stationCode, signal });
    const latestUpperDevice = await detailDeviceApi(modifiedUpperDevice, { stationCode, signal });
    const latestLowerDevice = await detailDeviceApi(modifiedLowerDevice, { stationCode, signal });
    return { upperDevice: latestUpperDevice, lowerDevice: latestLowerDevice };
  },
  onSuccess: (data) => {
    window.$loadingBar.finish();
    window.$message.success('解除成功');
    if (!data) return;
    const { upperDevice, lowerDevice } = data;
    if (!!upperDevice && !!lowerDevice) {
      deviceStore.patchDevice(station.value.code, { ...upperDevice });
      deviceStore.patchDevice(station.value.code, { ...lowerDevice });
    }
  },
  onError: (error) => {
    window.$loadingBar.error();
    if (isCancel(error)) return;
    console.error(error);
    const errorFeedback = parseErrorFeedback(error);
    window.$message.error(errorFeedback);
  },
});
onBeforeUnmount(() => {
  abortController.value.abort();
});
</script>

<template>
  <NCard v-if="showCard" hoverable size="small">
    <template #header>
      <span>端口数据</span>
    </template>
    <template #default>
      <!-- 遍历所有槽位 -->
      <template v-for="{ slotName, ports } in switchSlots" :key="slotName">
        <div style="padding: 8px 0">
          <span>{{ slotName }}（{{ ports.length }}个端口）</span>
          <!-- 端口布局 2行 至少12列 纵向排序 -->
          <div style="display: grid; grid-template-rows: repeat(2, auto); grid-auto-flow: column" :style="{ 'grid-template-columns': `repeat(${Math.max(ports.length / 2, 12)}, 1fr)` }">
            <template v-for="(port, index) in ports" :key="port.portName">
              <!-- 端口 -->
              <NPopover :delay="300">
                <template #trigger>
                  <!-- 最外层div宽度100% -->
                  <div
                    class="port"
                    style="height: 40px; box-sizing: border-box; display: flex; cursor: pointer"
                    :class="getPortClassName(port)"
                    @contextmenu="(payload) => onContextmenu(payload, port)"
                  >
                    <!-- 将端口号和状态指示器包裹起来 用于居中布局 -->
                    <div style="margin: auto; display: flex; flex-direction: column; align-items: center">
                      <div style="font-size: xx-small">{{ index }}</div>
                      <div class="indicator" style="width: 8px; height: 8px; border-radius: 50%" :class="getPortClassName(port)"></div>
                    </div>
                  </div>
                </template>
                <template #default>
                  <NDescriptions bordered size="small" label-placement="left" :column="1">
                    <NDescriptionsItem label="端口名称">{{ port.portName }}</NDescriptionsItem>
                    <NDescriptionsItem label="状态">{{ getPortStatusValue(port) }}</NDescriptionsItem>
                    <NDescriptionsItem label="状态变更时间">{{ port.lastChangeTime.replace('days', '天') }} 前</NDescriptionsItem>
                    <NDescriptionsItem label="上行速率">{{ transformPortSpeed(port, 'in') }}</NDescriptionsItem>
                    <NDescriptionsItem label="下行速率">{{ transformPortSpeed(port, 'out') }}</NDescriptionsItem>
                    <NDescriptionsItem label="总速率">{{ transformPortSpeed(port, 'total') }}</NDescriptionsItem>
                    <NDescriptionsItem v-if="!!port.opticalTemperature && port.opticalTemperature >= 0" label="光模块温度">{{ port.opticalTemperature }} ℃</NDescriptionsItem>
                    <NDescriptionsItem v-if="!!port.opticalVoltage && port.opticalVoltage >= 0" label="光模块电压">{{ port.opticalVoltage }} mV</NDescriptionsItem>
                    <NDescriptionsItem v-if="!!port.opticalBiasCurrent && port.opticalBiasCurrent >= 0" label="光模块偏置电流">{{ port.opticalBiasCurrent }} μA</NDescriptionsItem>
                    <NDescriptionsItem v-if="!!port.opticalReceivePower && port.opticalReceivePower >= 0" label="光模块接收功率">{{ port.opticalReceivePower / 100 }} dBm</NDescriptionsItem>
                    <NDescriptionsItem v-if="!!port.opticalTransmitPower && port.opticalTransmitPower >= 0" label="光模块发送功率">{{ port.opticalTransmitPower / 100 }} dBm</NDescriptionsItem>
                    <NDescriptionsItem label="关联设备">
                      <span v-if="getLowerDeviceByPort(port)" style="text-decoration: underline; cursor: pointer" @click="() => navigateToLowerDevice(port)">
                        {{ getLowerDeviceByPort(port)?.name || '-' }}
                      </span>
                      <span v-else>-</span>
                    </NDescriptionsItem>
                  </NDescriptions>
                </template>
              </NPopover>
            </template>
          </div>
        </div>
      </template>
    </template>
  </NCard>

  <SwitchPortLinkModal v-model:show="showModal" :ndm-device="ndmDevice" :station="station" :port="contextmenu.port" />

  <NDropdown
    placement="bottom-start"
    trigger="manual"
    :show="showContextmenu"
    :x="contextmenu.x"
    :y="contextmenu.y"
    :options="contextmenuOptions"
    @select="onSelectDropdownOption"
    @clickoutside="() => (showContextmenu = false)"
  />
</template>

<style scoped lang="scss">
.port {
  &.port-up {
    &:hover {
      background-color: #18a05816;
    }
  }

  &.port-down {
    &:hover {
      background-color: #d0305016;
    }
  }

  &.port-unknown {
    &:hover {
      background-color: #f0a02016;
    }
  }

  &:hover {
    transform: translateY(-2px);
    transition: transform 0.2s ease-in-out;
    box-shadow: v-bind('themeVars.boxShadow1');
  }

  .indicator {
    &.port-up {
      background-color: #18a058;
    }

    &.port-down {
      background-color: #d03050;
    }

    &.port-unknown {
      background-color: #f0a020;
    }
  }
}
</style>
