<script setup lang="ts">
import { computed, onMounted, ref, useTemplateRef } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { debounce } from 'lodash-es';

import SvgIcon from '@/components/SvgIcon.vue';
import { useDictData } from '@/hooks/dict-data';
import { useRequest } from '@/hooks/request';
import { t } from '@/i18n';
import { batchDeleleProtocolParam, getProtocolParamList } from '@/api';
import { DictCode, ProtocolRecognitionResult, ProtocolType } from '@/constants';

import CustomParams from './CustomParams.vue';
import SelectStandardParams from './SelectStandardParams.vue';

import type { FormInstance, TableProps } from 'ant-design-vue';
import type { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select';
import type { Key, SorterResult } from 'ant-design-vue/es/table/interface';
import type { CheckedType, FormRules, PageParams, PageSorts, ProtocolBaseInfo, ProtocolParamInfo } from '@/types';

interface Props {
  info: Partial<ProtocolBaseInfo>;
}

const props = defineProps<Props>();

const { dictData: protocolTypes, getDictData: getProtocolTypes } = useDictData(DictCode.AllProtocolType);
const { dictData: baudRateList, getDictData: getBaudRateList } = useDictData(DictCode.BaudRate);
const { dictData: readContinuousAddr, getDictData: getReadContinuousAddr } = useDictData(DictCode.ReadContinuousAddr);
const { dictData: byteOrderList, getDictData: getByteOrderList } = useDictData(DictCode.ByteOrder);
const { dictData: addrOrderList, getDictData: getAddrOrderList } = useDictData(DictCode.AddrOrder);

const isModbusRtuProtocol = computed(() => {
  return props.info.protocolType?.includes(ProtocolType.ModbusRTU);
});

const isModbusTcpProtocol = computed(() => {
  return props.info.protocolType?.includes(ProtocolType.ModbusTCP);
});

const isModbusProtocol = computed(() => {
  return isModbusRtuProtocol.value || isModbusTcpProtocol.value;
});

const isS7Protocol = computed(() => {
  return props.info.protocolType?.includes(ProtocolType.S7);
});

const formRef = ref<FormInstance>();

const rules = computed<FormRules<ProtocolBaseInfo>>(() => {
  return {
    protocolName: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.protocolName') }),
        trigger: 'blur',
      },
    ],
    protocolType: [
      {
        required: true,
        message: t('setupProtocol.plzSelectProtocolType'),
        trigger: 'blur',
      },
    ],
    deviceType: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.deviceType') }),
        trigger: 'blur',
      },
    ],
    dataBit: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.dataBit') }),
        trigger: 'blur',
      },
    ],
    parityBit: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.parityBit') }),
        trigger: 'blur',
      },
    ],
    stopBit: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.stopBit') }),
        trigger: 'blur',
      },
    ],
    baudRate: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.baudRate') }),
        trigger: 'blur',
      },
    ],
    dataSendInterval: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.dataSendInterval') }),
        trigger: 'blur',
      },
    ],
    highFreqSendInterval: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.highFreqSendInterval') }),
        trigger: 'blur',
      },
    ],
    readTimeout: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.readTimeout') }),
        trigger: 'blur',
      },
    ],
    nextDataReadDelay: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.nextDataReadDelay') }),
        trigger: 'blur',
      },
    ],
    nextRoundDataReadDelay: [
      {
        required: true,
        message: t('common.plzEnter', { name: t('setupProtocol.nextRoundDataReadDelay') }),
        trigger: 'blur',
      },
    ],
    byteOrder: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.byteOrder') }),
        trigger: 'blur',
      },
    ],
    addrOrder: [
      {
        required: true,
        message: t('common.plzSelect', { name: t('setupProtocol.addrOrder') }),
        trigger: 'blur',
      },
    ],
  };
});

const validateProtocolInfo = async () => {
  await formRef.value?.validate();
};

onMounted(() => {
  getProtocolTypes();
  getBaudRateList();
  getReadContinuousAddr();
  getByteOrderList();
  getAddrOrderList();
  getCurrentProtocolParams();
});

const handleReadContinuousAddrChange = (checked: CheckedType) => {
  const [first, second] = readContinuousAddr.value;

  if (checked === first?.dictValue) {
    props.info.readContinuousAddrCode = Number(first?.dictEngValue);
  } else {
    props.info.readContinuousAddrCode = Number(second?.dictEngValue);
  }
};

const handleByteOrderChange = (_value: SelectValue, option: DefaultOptionType) => {
  props.info.byteOrderCode = option.key;
};

const handleAddrOrderChange = (_value: SelectValue, option: DefaultOptionType) => {
  props.info.addrOrderCode = option.key;
};

const paramList = ref<ProtocolParamInfo[]>([]);
const paramTotal = ref(0);
const paramNameOrCode = ref('');
const pageParams = ref<PageParams>({
  pageIndex: 1,
  pageSize: 10,
  pageSorts: [
    {
      column: 'id',
      asc: true,
    },
  ],
});

const { isLoading, handleRequest } = useRequest();

const getCurrentProtocolParams = () => {
  handleRequest(async () => {
    const { id } = props.info;

    if (!id) {
      return;
    }

    const { records, total } = await getProtocolParamList({
      ...pageParams.value,
      baseInfoId: id,
      paramName: paramNameOrCode.value,
      paramCode: paramNameOrCode.value,
    });

    paramList.value = records;
    paramTotal.value = total;
  });
};

const handleParamNameOrCodeChange = debounce(() => {
  pageParams.value.pageIndex = 1;
  selectedParamIds.value = [];
  getCurrentProtocolParams();
}, 300);

const handleParamTableChange: TableProps['onChange'] = (pagination, filters, sorter) => {
  pageParams.value.pageIndex = pagination.current || 1;
  pageParams.value.pageSize = pagination.pageSize || 10;

  let sorts: SorterResult[];

  if (Array.isArray(sorter)) {
    sorts = sorter;
  } else {
    sorts = [sorter];
  }

  sorts = sorts.filter((item) => item.field !== undefined && item.order !== undefined);

  if (sorts.length) {
    pageParams.value.pageSorts = sorts.map<PageSorts>((item) => ({
      column: item.field as string,
      asc: item.order === 'ascend',
    }));
  } else {
    pageParams.value.pageSorts = [
      {
        column: 'id',
        asc: true,
      },
    ];
  }

  selectedParamIds.value = [];
  getCurrentProtocolParams();
};

const selectedParamIds = ref<number[]>([]);

const handleParamSelectChange = (selectedRowKeys: Key[]) => {
  selectedParamIds.value = selectedRowKeys as number[];
};

const deleteSelectedParams = () => {
  Modal.confirm({
    title: t('setupProtocol.confirmDeleteParams'),
    async onOk() {
      try {
        await batchDeleleProtocolParam(selectedParamIds.value);
        selectedParamIds.value = [];
        message.success(t('setupProtocol.deleteParamsSuccessful'));

        pageParams.value.pageIndex = 1;
        getCurrentProtocolParams();
      } catch (err) {
        message.error((err as Error).message);
        console.error(err);
      }
    },
  });
};

const customParamsRef = useTemplateRef('customParams');
const selectStandardParamsRef = useTemplateRef('selectStandardParams');

defineExpose({
  validateProtocolInfo,
});
</script>

<template>
  <div>
    <AForm ref="formRef" :model="info" :rules="rules" layout="vertical">
      <div class="protocol-label">{{ $t('common.basicInfo') }}</div>
      <ARow :gutter="33">
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.protocolName')" name="protocolName">
            <AInput v-model:value="info.protocolName" :placeholder="$t('common.plzEnter')" />
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.protocolType')" name="protocolType">
            <ASelect v-model:value="info.protocolType" :placeholder="$t('common.plzSelect')" disabled>
              <ASelectOption v-for="item in protocolTypes" :key="item.dictValueId" :value="item.dictValue">
                {{ item.dictValue }}
              </ASelectOption>
            </ASelect>
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.deviceType')" name="deviceType">
            <ASelect v-model:value="info.deviceType" :placeholder="$t('common.plzSelect')" disabled>
              <!-- <ASelectOption :value=""></ASelectOption> -->
            </ASelect>
          </AFormItem>
        </ACol>
        <ACol v-if="isModbusRtuProtocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.dataBit')" name="dataBit">
            <ARadioGroup v-model:value="info.dataBit">
              <ARadio :value="5">5</ARadio>
              <ARadio :value="6">6</ARadio>
              <ARadio :value="7">7</ARadio>
              <ARadio :value="8">8</ARadio>
            </ARadioGroup>
          </AFormItem>
        </ACol>
        <ACol v-if="isModbusRtuProtocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.parityBit')" name="parityBit">
            <ARadioGroup v-model:value="info.parityBit">
              <ARadio value="N">N</ARadio>
              <ARadio value="O">O</ARadio>
              <ARadio value="E">E</ARadio>
            </ARadioGroup>
          </AFormItem>
        </ACol>
        <ACol v-if="isModbusRtuProtocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.stopBit')" name="stopBit">
            <ARadioGroup v-model:value="info.stopBit">
              <ARadio value="1">1</ARadio>
              <ARadio value="1.5">1.5</ARadio>
              <ARadio value="2">2</ARadio>
            </ARadioGroup>
          </AFormItem>
        </ACol>
        <ACol v-if="isModbusRtuProtocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.baudRate')" name="baudRate">
            <ASelect v-model:value="info.baudRate" :placeholder="$t('common.plzSelect')">
              <ASelectOption v-for="item in baudRateList" :key="item.dictValueId" :value="Number(item.dictValue)">
                {{ item.dictValue }}
              </ASelectOption>
            </ASelect>
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.dataSendInterval')" name="dataSendInterval">
            <AInputNumber
              v-model:value="info.dataSendInterval"
              :placeholder="$t('common.plzEnter')"
              addon-after="s"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.highFreqSendInterval')" name="highFreqSendInterval">
            <AInputNumber
              v-model:value="info.highFreqSendInterval"
              :placeholder="$t('common.plzEnter')"
              addon-after="s"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.readTimeout')" name="readTimeout">
            <AInputNumber
              v-model:value="info.readTimeout"
              :placeholder="$t('common.plzEnter')"
              addon-after="ms"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.nextDataReadDelay')" name="nextDataReadDelay">
            <AInputNumber
              v-model:value="info.nextDataReadDelay"
              :placeholder="$t('common.plzEnter')"
              addon-after="ms"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.nextRoundDataReadDelay')" name="nextRoundDataReadDelay">
            <AInputNumber
              v-model:value="info.nextRoundDataReadDelay"
              :placeholder="$t('common.plzEnter')"
              addon-after="ms"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
        <ACol v-if="!isS7Protocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.readContinuousAddr')" name="readContinuousAddr">
            <ASwitch
              v-model:checked="info.readContinuousAddr"
              :checked-value="readContinuousAddr[1]?.dictValue"
              :un-checked-value="readContinuousAddr[0]?.dictValue"
              @change="handleReadContinuousAddrChange"
            />
          </AFormItem>
        </ACol>
        <ACol v-if="!isS7Protocol" flex="20%">
          <AFormItem :label="$t('setupProtocol.readContinuousAddrLength')" name="readContinuousAddrLength">
            <AInputNumber
              v-model:value="info.readContinuousAddrLength"
              :placeholder="$t('common.plzEnter')"
              :min="0"
              :precision="0"
            />
          </AFormItem>
        </ACol>
      </ARow>
      <div v-if="!isS7Protocol" class="protocol-label advanced-label">{{ $t('common.advancedSettings') }}</div>
      <ARow v-if="!isS7Protocol" :gutter="33">
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.byteOrder')" name="byteOrder">
            <ASelect
              v-model:value="info.byteOrder"
              :placeholder="$t('common.plzSelect')"
              @change="handleByteOrderChange"
            >
              <ASelectOption v-for="item in byteOrderList" :key="item.dictEngValue" :value="item.dictValue">
                {{ item.dictValue }}
              </ASelectOption>
            </ASelect>
          </AFormItem>
        </ACol>
        <ACol flex="20%">
          <AFormItem :label="$t('setupProtocol.addrOrder')" name="addrOrder">
            <ASelect
              v-model:value="info.addrOrder"
              :placeholder="$t('common.plzSelect')"
              @change="handleAddrOrderChange"
            >
              <ASelectOption v-for="item in addrOrderList" :key="item.dictEngValue" :value="item.dictValue">
                {{ item.dictValue }}
              </ASelectOption>
            </ASelect>
          </AFormItem>
        </ACol>
      </ARow>
    </AForm>
    <div class="protocol-label params-label">
      {{ $t('setupProtocol.protocolParams') }}
      <span class="protocol-params-tip">
        <SvgIcon name="info-cirlce-o" />
        {{ $t('setupProtocol.protocolTypeTipForPlc') }}
      </span>
    </div>
    <div class="protocol-params-query">
      <AInput
        v-model:value="paramNameOrCode"
        class="protocol-params-input"
        :placeholder="$t('setupProtocol.plzEnterParamOrCode')"
        allow-clear
        @change="handleParamNameOrCodeChange"
      >
        <template #prefix>
          <SvgIcon name="search-o" :size="12" color="#B2B2B2" />
        </template>
      </AInput>
      <div>
        <AButton danger :disabled="selectedParamIds.length === 0" @click="deleteSelectedParams">
          {{ $t('setupProtocol.deleteSelected') }}
        </AButton>
        <AButton @click="customParamsRef?.showView">{{ $t('setupProtocol.addCustomParams') }}</AButton>
        <AButton @click="selectStandardParamsRef?.showView" type="primary">
          {{ $t('setupProtocol.addStandardParams') }}
        </AButton>
      </div>
    </div>
    <ATable
      :data-source="paramList"
      row-key="id"
      :row-selection="{ selectedRowKeys: selectedParamIds, onChange: handleParamSelectChange }"
      :scroll="{ x: 2800 }"
      :loading="isLoading"
      :pagination="{
        current: pageParams.pageIndex,
        pageSize: pageParams.pageSize,
        total: paramTotal,
        showSizeChanger: true,
        hideOnSinglePage: false,
        showTotal: (total) => $t('common.pageTotal', { total }),
      }"
      @change="handleParamTableChange"
    >
      <ATableColumn
        :title="$t('setupProtocol.protocolParamFields.customParamName')"
        data-index="paramName"
        :width="180"
        fixed="left"
      />
      <ATableColumn
        :title="$t('setupProtocol.protocolParamFields.result')"
        data-index="recognizeResult"
        :width="110"
        :sorter="true"
      >
        <template #default="{ record }">
          <div
            :class="[
              'protocol-recognize-result',
              {
                'protocol-recognize-success': record.recognizeResult === ProtocolRecognitionResult.Success,
                'protocol-recognize-unrecognized': record.recognizeResult === ProtocolRecognitionResult.Unrecognized,
                'protocol-recognize-multiple-results':
                  record.recognizeResult === ProtocolRecognitionResult.MultipleResults,
              },
            ]"
          >
            {{ record.recognizeResult }}
          </div>
        </template>
      </ATableColumn>
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.platformParamName')"
        data-index="platformParamName"
        :width="170"
      />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.platformParamCode')"
        data-index="platformParamCode"
        :width="280"
      />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.gatewayParamName')"
        data-index="gatewayParamName"
        :width="200"
      />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.gatewayParamCode')"
        data-index="gatewayParamCode"
        :width="200"
      />
      <ATableColumn :title="$t('setupProtocol.protocolParamFields.unit')" data-index="unit" />
      <ATableColumn :title="$t('setupProtocol.protocolParamFields.module')" data-index="module" />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.readWriteType')"
        data-index="readWriteType"
      />
      <ATableColumn
        v-if="isS7Protocol"
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.registerType')"
        data-index="registerType"
      />
      <ATableColumn
        v-if="isS7Protocol"
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.addrNumber')"
        data-index="addrNumber"
      />
      <ATableColumn
        v-if="isModbusProtocol"
        :title="$t('setupProtocol.protocolParamFields.readFunctionCode')"
        data-index="readFuncCode"
      />
      <ATableColumn
        v-if="isModbusProtocol"
        :title="$t('setupProtocol.protocolParamFields.writeFunctionCode')"
        data-index="writeFuncCode"
      />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.registerAddress')"
        data-index="registerAddr"
      />
      <ATableColumn
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.parsingType')"
        data-index="parsingType"
        :width="150"
      />
      <ATableColumn
        v-if="isS7Protocol"
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.wordLength')"
        data-index="wordLength"
      />
      <ATableColumn
        v-if="isS7Protocol"
        :title="$t('setupProtocol.protocolParamFields.quantity')"
        data-index="quantity"
      />
      <ATableColumn
        v-if="isModbusProtocol"
        class="table-column-item-required"
        :title="$t('setupProtocol.protocolParamFields.addressLength')"
        data-index="addrLength"
      />
      <ATableColumn
        v-if="isModbusProtocol"
        :title="$t('setupProtocol.protocolParamFields.dataType')"
        data-index="dataType"
      />
      <ATableColumn :title="$t('setupProtocol.protocolParamFields.coefficient')" data-index="coefficient" />
      <ATableColumn
        :title="$t('setupProtocol.protocolParamFields.isHighFrequencyParameter')"
        data-index="isHighFreqParam"
        :width="150"
      />
      <ATableColumn
        :title="$t('setupProtocol.protocolParamFields.writeCalculationFormula')"
        data-index="writeCalcFormula"
      />
      <ATableColumn
        :title="$t('setupProtocol.protocolParamFields.readCalculationFormula')"
        data-index="readCalcFormula"
      />
      <ATableColumn :title="$t('setupProtocol.protocolParamFields.decimalPlaces')" data-index="decimalPlace" />
    </ATable>
    <CustomParams
      ref="customParams"
      :protocol-id="info.id"
      :is-modbus-rtu-protocol="isModbusRtuProtocol"
      :is-modbus-tcp-protocol="isModbusTcpProtocol"
      :is-s7-protocol="isS7Protocol"
      @refresh-list="getCurrentProtocolParams"
    />
    <SelectStandardParams ref="selectStandardParams" :protocol-id="info.id" @refresh-list="getCurrentProtocolParams" />
  </div>
</template>

<style lang="scss" scoped>
:deep(.ant-form) {
  .ant-form-item {
    width: 192px;
  }

  .ant-input-number-group .ant-input-number-group-addon {
    padding-inline: 14px;
    color: #333;
    background-color: initial;
  }

  .ant-radio-wrapper span.ant-radio + * {
    padding-inline: 6px;
  }

  .ant-input-number {
    width: 100%;
  }

  .ant-switch {
    height: 24px;
  }

  .ant-switch-handle {
    width: 20px;
    height: 20px;
  }

  .ant-switch.ant-switch-checked .ant-switch-handle {
    inset-inline-start: calc(100% - 22px);
  }
}

.protocol-label {
  padding-left: 8px;
  margin-bottom: 24px;
  font-size: 16px;
  font-weight: 500;
  line-height: 24px;
  color: #000;
  border-left: 2px solid var(--antd-color-primary);

  &.advanced-label,
  &.params-label {
    margin-top: 16px;
  }

  &.params-label {
    display: flex;
    align-items: center;
  }
}

.protocol-params-input {
  width: 256px;
}

.protocol-params-tip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  font-size: 12px;
  font-weight: 500;
  line-height: 22px;
  color: #b2b2b2;

  i {
    margin-right: 5px;
    margin-left: 16px;
  }
}

.protocol-params-query {
  display: flex;
  justify-content: space-between;
  margin-bottom: 16px;

  button + button {
    margin-left: 16px;
  }
}

:deep(.ant-table-wrapper) .ant-table-container {
  .ant-table-thead > tr > th {
    color: #333;
    background: #f5f6f7;
    border-bottom: none;
  }

  .ant-table-thead
    > tr
    > th.ant-table-cell:not(:last-child, .ant-table-selection-column, .ant-table-row-expand-icon-cell, [colspan]) {
    &::before {
      background-color: transparent;
    }

    &.table-column-item-required::before {
      position: initial;
      width: auto;
      font-size: 14px;
      line-height: 24px;
      color: #f56c6c;
      content: '*';
      transform: initial;
    }
  }
}

.protocol-recognize-result {
  height: 24px;
  padding: 2px 8px;
  font-size: 12px;
  line-height: 20px;
  text-align: center;
  border: 1px solid var(--antd-color-border);
  border-radius: 4px;

  &.protocol-recognize-success {
    color: #52c41a;
    background: #f6ffed;
    border-color: #b7eb8f;
  }

  &.protocol-recognize-unrecognized {
    color: #f56c6c;
    background: #fff1f0;
    border-color: #ffa39e;
  }

  &.protocol-recognize-multiple-results {
    color: var(--antd-color-primary);
    background: var(--antd-color-primary-opacity-10);
    border-color: var(--antd-color-primary-opacity-60);
  }
}
</style>
