<template>
  <v-dialog v-model="props.isExcelUploadModal" persistent width="32.8125rem">
    <div class="v-common-dialog-wrapper custom-dialog">
      <div class="modal-tit">
        <strong>엑셀업로드</strong>
        <button class="btn-close" @click="$emit('closeModal')"></button>
      </div>
      <div class="v-common-dialog-content">
        <div class="excel-step">
          <div class="excel-step-box">
            <div class="excel-step-top">
              <span class="step" style="width:3.9375rem">Step1</span>
              <strong>양식 다운로드</strong>
            </div>
            <div class="excel-step-btm">
              <div class="excel--inner--content">
                <div class="ico"></div>
                  <div class="desc">
                    <p>일괄 등록 양식을 다운로드 받아 양식에 맞게 데이터를 입력 하고 하단의 첨부파일 등록을 통해 일괄 등록 신청을 진행하세요.</p>
                    <v-btn class="custom-btn mini btn-gray" @click="fnExcelFormDownload">엑셀 다운로드</v-btn>
                  </div>
              </div>

            </div>
          </div>
          <div class="excel-step-box">
            <div class="excel-step-top">
              <span class="step">Step2</span>
              <strong>엑셀 일괄 업로드</strong>
            </div>
            <div class="excel-step-btm">
              <div class="step-bg-box type2">
                <p>양식에 맞게 작성을 하였다면 해당 파일을 업로드하세요.</p>
                <p class="txt2">(파일 등록 시 STEP 03에서 결과 확인 가능)</p>
                <div class="add-file">
                  <v-file-input
                    hide-details
                    @change="fnFileChange"
                    @click:clear="fnClearFile"
                  >
                    <template v-slot:label>                        
                      <v-btn class="custom-btn mini btn-gray" style="max-width:7.0625rem!important; height:2.25rem;">엑셀 일괄 등록</v-btn>
                    </template>
                  </v-file-input>
                </div>
              </div>
            </div>
          </div>
          <div class="excel-step-box">
            <div class="excel-step-top">
              <span class="step">Step3</span>
              <strong>일괄 등록 결과 확인</strong>
            </div>
            <div class="excel-step-btm">
              <div class="tbl-wrapper">
                <div class="tbl-wrap">
                  <!-- ag grid -->
                  <ag-grid-vue
                    style="width:100%; height:calc(5 * 3.15rem);"
                    class="ag-theme-quartz"
                    :gridOptions="gridOptions2"                                
                    :defaultColDef="defaultColDef"
                    :columnDefs="tblHeaders2"
                    :rowData="tblItems2"
                    :suppressPaginationPanel="true"
                    :overlayNoRowsTemplate="getExcelResultMsg"
                    @grid-ready="fnOnGridReady"
                  >                            
                  </ag-grid-vue>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="btn-wrap">
        <v-btn
          class="custom-btn btn-white mini"
          @click="$emit('closeModal')"
        >
          <i class="ico"></i>
          취소
        </v-btn>
        <v-btn class="custom-btn btn-blue mini" :disabled="isUploadStep3 === false" @click="fnUploadProc">
          <i class="ico"></i>
          확인
        </v-btn>
      </div>
    </div>
  </v-dialog>
</template>

<script setup>
  /***********************
   *     import
   ************************/
  import { read, utils, writeFile } from "xlsx";
  import { useI18n } from "vue-i18n"
  import useUtil from "@/composables/useUtil"
  import useValid from "@/composables/useValid"
  import "ag-grid-community/styles/ag-grid.css"
  import "ag-grid-community/styles/ag-theme-quartz.css"
  import { AgGridVue } from "ag-grid-vue3"
  import customTextDiv from '@/components/design/customTextDiv'
  /***********************
   *     plugins inject
   ************************/
  const { $toast, $log, $dayjs, $eventBus } = useNuxtApp()

  // props
  const props = defineProps({
    isExcelUploadModal: Boolean,
    menu: String,
    imsiPrefixList: Array
  });
  // 참조가능 데이터 설정
  defineExpose({
    fnInit,
  })
  // 발신 이벤트 선언
  const emit = defineEmits(["closeModal", "uploadProc"])
  const i18n = useI18n()

  /***********************
   *     data & created
   ************************/
  const tblHeaders2 = ref([
    { headerName: 'No', field: 'no'},
    { headerName: '엑셀 시트', field: 'headerName'},
    { headerName: '엑셀 행', field: 'cellNum'},
    { headerName: '실패 원인', field: 'errorMsg', cellRenderer : customTextDiv},
  ])

  const tblItems2 = ref([])

  const excelFileNames = ref([])
  const invalidData = ref([])   // 등록실패 데이터
  const isUploadStep2 = ref(false)  // 파일 등록 여부
  const isUploadStep3 = ref(false)  // 파일 데이터 유효성 통과 여부
  const uploadParams = ref(null)
  const remToPx = () => parseFloat(getComputedStyle(document.documentElement).fontSize);
  const rowHeightRem = 2.65; // 원하는 rem 값
  const rowHeightPx = rowHeightRem * remToPx();
  // gridOption2
  const gridOptions2 = {
    columnDefs: tblHeaders2.value,
    rowData: tblItems2.value,      // 테이블 데이터
    rowSelection : 'multiple',
    autoSizeStrategy: {
      type: "fitGridWidth", // width맞춤
    },  
    headerHeight : rowHeightPx,
    rowHeight: rowHeightPx,
    pagination: true,
    // overlayNoRowsTemplate: '<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>',
    suppressPaginationPanel: true,    // 하단 default 페이징 컨트롤 숨김
    suppressRowClickSelection: true,  // 행 클릭 체크박스 무시
    localeText: {
      noRowsToShow: i18n.t('common.noData')
    },
  }
  const excelResultMsg = ref('<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>')
  // grid default 옵션
  const defaultColDef = ref({
    lockVisible: true, // 열을 그리드 밖으로 꺼내지 않음
  })
  const gridApi = shallowRef()
  const isExcel = ref(false)
  const getExcelResultMsg = computed(() => {
    if(!isExcel.value) {
      return '<span class="no--rows--custom"><i class="ico-excel"></i>※ 엑셀 파일을 일괄 등록하세요.</span>'
    }else{
      return '<span class="no--rows--custom"><i class="ico-excel"></i>※ 등록한 파일의 데이터가 정상입니다.</span>'
    }
  })
  /***********************
   *     Methods
   ************************/
  function fnInit() {
    tblItems2.value = []
    isUploadStep2.value = false
    isUploadStep3.value = false
    uploadParams.value = null
    invalidData.value = []
  }
  /**
   * 엑셀 양식 다운로드
   */
  function fnExcelFormDownload(){
    let arrForm = ''
    let arrGuide = ''
    let sheetTitle = ''
    let fileName = ''
    if(props.menu === 'userMgmt') {
      arrForm = fnSetUserInsertForm()
      arrGuide = fnSetUserGuideForm()
      sheetTitle = 'USERS'
      fileName = '가입자일괄등록양식'
    }else{
      arrForm = fnSetNeInsertForm()
      arrGuide = fnSetNeGuideForm()
      sheetTitle = 'NE'
      fileName = 'NE일괄등록양식'
    }
    useUtil.fnExcelFormDown(arrForm, arrGuide, sheetTitle, fileName)
  }
  // 가입자등록 양식 데이터
  function fnSetUserInsertForm() {
    let arr = []
    arr.push([
      "tenantName",
      "imsi",
      "msisdns",
      "skey",
      "opc",
      "uplink",
      "downlink",
      "singleNssais",
      "defaultNssais",
      "dnnConf_1_nssai",
      "dnnConf_1_dnn",
      "dnnConf_2_nssai",
      "dnnConf_2_dnn",
      "dnnConf_3_nssai",
      "dnnConf_3_dnn",
      "dnnConf_4_nssai",
      "dnnConf_4_dnn",
      "dnnConf_5_nssai",
      "dnnConf_5_dnn",
      "dnnConf_6_nssai",
      "dnnConf_6_dnn",
      "dnnConf_7_nssai",
      "dnnConf_7_dnn",
      "dnnConf_8_nssai",
      "dnnConf_8_dnn",
      "dnnConf_9_nssai",
      "dnnConf_9_dnn",
      "dnnConf_10_nssai",
      "dnnConf_10_dnn",
    ])
    arr.push(["KT", "45043000", "", "", "", ""])

    return arr
  }
  // 가입자등록 양식 유효성 데이터
  function fnSetUserGuideForm() {
    let arr = []
    arr.push(["COLUMN NAME", "입력 설명"])
    arr.push(["tenantName", "테넌트 이름"])
    arr.push(["imsi","IMSI, 앞 자리 값은 테넌트에 할당된 번호여야 하고 테넌트 이름과 함께 유효성 체크를 한다.",])
    arr.push(["msisdns","pattern: '(msisdn-[0-9]{5,15}|extid-.+@.+|.+)' 콤마(,)로 구분하여 여러개 입력이 가능하다.   -- ex) 1000,2000,3000",])
    arr.push(["skey", ""])
    arr.push(["opc", ""])
    arr.push(["uplink","subscribed-UeAmbr Uplink, pattern: '^\\d+(\\.\\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$'   -- ex) 1024 Mbps",])
    arr.push(["downlink","subscribed-UeAmbr Downlink, pattern: '^\\d+(\\.\\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$'   -- ex) 1024 Mbps",])
    arr.push(["singleNssais","List of non default Single Nssais. 콤마(,)로 구분하여 여러개 입력이 가능하다.   -- ex) 1000,2000,3000",])
    arr.push(["defaultNssais","A list of Single Nssais used as default. 콤마(,)로 구분하여 여러개 입력이 가능하다.   -- ex) 1000,2000,3000",])
    arr.push(["dnnConf/{index}/nssai","dnnConf는 최대 5개의 nssai 와 dnn 쌍으로 입력 가능하다.",])
    arr.push(["dnnConf/{index}/dnn","반드시 1쌍 이상으로 index를 유의해서 입력해야한다. 쌍이 성립되지 않으면 데이터는 처리하지 않는다.",])
    return arr
  }
  // NE등록 양식 데이터
  function fnSetNeInsertForm(){
    let arr = []
    arr.push([
      'NE Name',
      'Tenant Name',
      'NE Group Name',
      'NE Type',
      'NE ID',
      'Connected UPF',
      'EMS Name',
      'NE Address',
      'NE Location (Latitude)',
      'NE Location (Longitude)',
      'Description'
    ])
    arr.push(["nename01", "", "", "", "", "", "", "", "", "", ""])

    return arr
  }
  // NE등록 양식 유효성 데이터
  function fnSetNeGuideForm(){
    let arr = []
    arr.push(["COLUMN NAME", "입력 설명"])
    arr.push(["NE Name", "NE 이름"])
    arr.push(["Tenant Name","테넌트 이름",])
    arr.push(["NE Group Name","NE 그룹 이름",])
    arr.push(["NE Type", "NE 유형"])
    arr.push(["NE ID", ""])
    arr.push(["Connected UPF","연동 UPF",])
    arr.push(["EMS Name","수집 시스템",])
    arr.push(["NE Address","NE 주소",])
    arr.push(["NE Location (Latitude)","NE 주소 위도",])
    arr.push(["NE Location (Longitude)","NE 주소 경도",])
    arr.push(["Description","설명",])
    return arr
  }
  /**
   * 파일 업로드 이벤트
   */
  function fnFileChange(event) {
    let isFiles = event.target.files.length > 0
    if(isFiles) {
      isUploadStep2.value = true // 파일 업로드한 상태
      isUploadStep3.value = false

      let excelFileName = event.target.files[0].name;
      excelFileNames.value = excelFileName

      if (excelFileName.includes("xlsx") === true ||excelFileName.includes("xls") === true) {
        const file = event.target.files[0]
        let reader = new FileReader()
        reader.onload = () => {
          let data = reader.result

          let workbook
          try {
            workbook = read(data, { type: "binary" })
          } catch (error) {
            alert("파일을 읽을 수 없습니다.")
            return;
          }
          let sheetName = workbook.SheetNames[0]
          let worksheet = workbook.Sheets[sheetName]
          const rowObject = utils.sheet_to_json(worksheet)

          // 엑셀 데이터를 JSON으로 변환하면서 셀 위치 정보를 추가
          const jsonData = utils.sheet_to_json(worksheet, { header: 1 })
          
          console.log('%c rowObject########' ,'color:#bada55', rowObject)
          console.log('%c jsonData' ,'color:#bada55', jsonData)
          // 빈배열제거
          const newData = jsonData.filter(el => el.length)
          const headers = newData[0]

          let rowData = newData.slice(1).map((row, rowIndex) => {
            const obj = {}
            headers.forEach((header, colIndex) => {
              const cell = row[colIndex] !== undefined ? row[colIndex] : '' // 값을 설정하고 빈 값은 빈 문자열로
              obj[header] = {
                value: cell,
                cell: utils.encode_cell({ r: rowIndex + 1, c: colIndex }),
              }
            })
            return obj
          })
          
          let invalidDataList = []

          if(props.menu === 'userMgmt') {
            // 사용자 일괄 등록
            invalidDataList = userValidateData(headers, rowData)
            if(invalidDataList.length === 0){
              // 유효성 모두 통과
              let param = fnUserMakeParam(rowData)
              uploadParams.value = _cloneDeep(param)
            } else {
              uploadParams.value = null
            }
          }else{
            // NE 일괄 등록
            invalidDataList = neValidateData(headers, rowData)//neValidateData(rowData)

            if(invalidDataList.length === 0){
              let param = fnNeMakeParam(rowData)
              uploadParams.value = _cloneDeep(param)
            } else {
              uploadParams.value = null
            }
          }
          // 유효성 체크 리스트

          invalidData.value = _cloneDeep(invalidDataList)

          if(invalidDataList.length === 0) {
            isExcel.value = true
            // gridApi.value.api.setRowData(rowData.value);
            setTimeout(() => {
              gridApi.value.showNoRowsOverlay(); 
            }, 1);
          }else{
            isExcel.value = false
          }
          // 양식에러 테이블 데이터 목록
          tblItems2.value = _cloneDeep(invalidData.value)
          isUploadStep3.value = invalidData.value.length === 0
        }
        reader.readAsBinaryString(file)
      } else {
        alert("파일 확장자를 확인하세요(.xlsx, .xls)")
      }
    }
    else{
      isUploadStep2.value = false
      isUploadStep3.value = false
      fnClearFile()
    }
  }
  /**
   * 사용자 일괄등록 유효성 검사
   */
  function userValidateData(headers, data) {
    let invalidData = [];
    
    // 유효성 체크 항목들 
    let regexMap = {
      tenantName: fnBusinessNameChk,  // 테넌트명 체크
      uplink: fnLinkValidChk,         // 업로드 속도값 체크 
      downlink: fnLinkValidChk,       // 다운로드 속도값 체크
      imsi: fnImsiChk,                // IMSI체크
      msisdns: fnMsisdnsChk,          // MSISDN 체크
      // defaultNssais: fnDefaultNssaisChk, // Default NSSAI 체크
    };

    const dnnConf = [
      'dnnConf_1_nssai', 'dnnConf_1_dnn',
      'dnnConf_2_nssai', 'dnnConf_2_dnn',
      'dnnConf_3_nssai', 'dnnConf_3_dnn',
      'dnnConf_4_nssai', 'dnnConf_4_dnn',
      'dnnConf_5_nssai', 'dnnConf_5_dnn',
      'dnnConf_6_nssai', 'dnnConf_6_dnn',
      'dnnConf_7_nssai', 'dnnConf_7_dnn',
      'dnnConf_8_nssai', 'dnnConf_8_dnn',
      'dnnConf_9_nssai', 'dnnConf_9_dnn',
      'dnnConf_10_nssai', 'dnnConf_10_dnn',
    ]

    // dnnConf키 수집 수
    let dnnConfKeyCnt = 0

    // 필수 키값 목록
    const requiredFields = ['tenantName', 'imsi', 'msisdns', 'skey', 'opc', 'uplink', 'downlink', 'singleNssais', 'defaultNssais']

    // IMSI 중복체크 데이터
    const imsiList = data.map(row => {
      if(row.hasOwnProperty('imsi')) {
        return row.imsi.value.toString()
      }else return []
    })
    // flatMap으로 평탄화 작업
    let num = 0
    invalidData = data.flatMap((item, index) => {
      // row 한바퀴 수행 후 초기화
      dnnConfKeyCnt = 0
      validDnnConf.value = []
      dnnConfigCnt.value = 0
      return Object.entries(item).flatMap(([key, { value, cell }]) => {
        // 필수 값 체크 dnnConf키가 아니고 필수키가 없는 경우
        if (!dnnConf.includes(key) && !requiredFields.includes(key)) {
          return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' }
        }

        if (requiredFields.includes(key) && useUtil.isNull(value)) {
          // 헤더 키는 존재하나 데이터가 없는 경우
          return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: 'noData' }
        }
        
        if(dnnConf.includes(key)) {
          dnnConfKeyCnt++
        }
        if(dnnConfKeyCnt == 19) {
          const result = fnDnnValidation(item)
          if(result !==true){
            return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
          }
        }

        // IMSI 중복 체크
        if (key === 'imsi') {
          const result = fnImsiDupChk(item, key, imsiList);
          if (result !== true) {
            return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
          }
        }

        const regex = regexMap[key];
        if (regex) {
          const result = regex(item, key);
          
          if (result !== true) {
            // 정규식 통과 실패
            return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: result }
          }
        }
        return []
      })
    })

    return invalidData
  }
  /**
   * 사용자일괄등록 param목록
   */
  function fnUserMakeParam(rowData){
    let params = []
    rowData.forEach((row, idx) => {
      let msisdnsArray = row.msisdns.value.toString().replace(/ /g, "").split(",") // 공백제거 후, split
      let singleNssaisArray = []
      if (row.singleNssais.value != undefined)
        singleNssaisArray = row.singleNssais.value.toString().replace(/ /g, "").split(",")
      let defaultNssaisArray = row.defaultNssais.value.toString().replace(/ /g, "").split(",")
      let item = {
        accountId: useAuthStore().getAccountId,
        tenantName: row.tenantName.value,
        imsi: row.imsi.value.toString(),
        msisdns: msisdnsArray, //item.msisdns,
        skey: row.skey.value.toString(),
        opc: row.opc.value.toString(),
        singleNssais: singleNssaisArray, //item.singleNssais,
        defaultNssais: defaultNssaisArray, //item.defaultNssais,
        uplink: row.uplink.value,
        downlink: row.downlink.value,
        dnnConf: row.dnnConf
      }

      params.push(item)
    })
    return params
  }
  /**
   * 사업자명 체크
   */
  function fnBusinessNameChk(row, key){
    const userPriority = useAuthStore().getAccountRole
    const userBusinessInfo = useAuthStore().getAuth.tenantName
    if (userPriority == "ADMIN") {
      // ADMIN 권한인 경우 등록가능, 테넌트 명이 존재하고 나의 테넌트명과 다르면 등록안된다.
      if (userBusinessInfo.length > 0 && userBusinessInfo != row[key].value) {
        return 'invalid_tenantName'
      }else{
        return true
      }
      // return true
    }else{
      return 'No permission'
    }
  }
  /**
   * 업다운링크 체크
   */
  function fnLinkValidChk(row, key){
    let linkRegex = /^\d+(\.\d+)? (bps|Kbps|Mbps|Gbps|Tbps)$/
    return linkRegex.test(row[key].value) ? true : 'Invalid link'
  }
  // 현재 업로드한 엑셀파일 데이터에서 imsi값 중복체크 
  function fnImsiDupChk(row, key, imsiList ){
    const imsiValue = row[key].value.toString()
    return imsiList.indexOf(imsiValue) !== imsiList.lastIndexOf(imsiValue) ? 'Duplicate IMSI' : true
  }
  /**
   * imsi체크
   * key = 'imsi'
   */
  function fnImsiChk(row, key){
    let imsiPrefix = row[key].value.toString().substr(0, 8) // 엑셀에 입력한 imsi값 앞에서 8자리 가져온다
    let notMatchFlag = false
    props.imsiPrefixList.some((element) => {
      if (element.key == row.tenantName.value) {
        // imsiPrefix의 테넌트명과 엑셀에 입력된 테넌트명이 같은 경우
        if (imsiPrefix == element.value) {
          // 입력한 고정imsi값이 imsiPrefix값과 같은 경우
          notMatchFlag = false
        }
        return true
      }
    })
    return notMatchFlag ? 'Invalid_imsi' : true
  }
  function fnMsisdnsChk(row, key){
    const regex = /(msisdn-\d{5,15}|extid-.+@.+|.+)/

    const items = row[key].value.split(',').map(item => item.trim())

    for (const item of items) {
      if (!regex.test(item)) {
        return `Invalid entry found: "${item}"`
      }
    }

    return true
  }
  /**
   * dnnConfig 유효성 검사
   */
  const validDnnConf = ref([])
  const dnnConfigCnt = ref(0)

  function fnDnnValidation(row){
    const pairs = [
      { key1: 'dnnConf_1_nssai', key2: 'dnnConf_1_dnn' },
      { key1: 'dnnConf_2_nssai', key2: 'dnnConf_2_dnn' },
      { key1: 'dnnConf_3_nssai', key2: 'dnnConf_3_dnn' },
      { key1: 'dnnConf_4_nssai', key2: 'dnnConf_4_dnn' },
      { key1: 'dnnConf_5_nssai', key2: 'dnnConf_5_dnn' },
      { key1: 'dnnConf_6_nssai', key2: 'dnnConf_6_dnn' },
      { key1: 'dnnConf_7_nssai', key2: 'dnnConf_7_dnn' },
      { key1: 'dnnConf_8_nssai', key2: 'dnnConf_8_dnn' },
      { key1: 'dnnConf_9_nssai', key2: 'dnnConf_9_dnn' },
      { key1: 'dnnConf_10_nssai', key2: 'dnnConf_10_dnn' },
    ]
    pairs.forEach((item, idx) => {
        if (!useUtil.isNull(row[item.key1].value) && !useUtil.isNull(row[item.key2].value)) {
          validDnnConf.value[dnnConfigCnt.value++] = {
            nssai: row[item.key1].value.toString(),
            dnn: row[item.key2].value.toString(),
          }
        } 
    })
    if (dnnConfigCnt.value < 1) {
      return 'one NSSAI-DNN pair required'
    }else{
      row.dnnConf=_cloneDeep(validDnnConf.value)
    }
    return true
  }
  /**
   * ne일괄등록 param목록
   */
  function fnNeMakeParam(rowData){
    let params = []
    rowData.forEach((row, idx) => {
      let item = {
        accountId: useAuthStore().getAccountId,
        accountName: useAuthStore().getAccountName,
        tenantName: row['Tenant Name'].value,
        neGroup: row['NE Group Name'].value,
        neName: row['NE Name'].value,
        neId:  row['NE ID'].value,
        neType:  row['NE Type'].value,
        upfNum:  row['Connected UPF'].value,
        emsName:  row['EMS Name'].value,
        neAddress: row['NE Address'].value,
        neLocLatitude: row['NE Location (Latitude)'].value,
        neLocLongitude: row['NE Location (Longitude)'].value,
        description: row['Description'].value
      }
      params.push(item)
    })
    return params
  }
  /**
   * ne일괄등록 유효성 검사
   */
  function neValidateData(headers, rowData){
    let invalidData = []
    const requiredFields = ['NE Name', 'Tenant Name', 'NE Group Name', 'NE Type', 'NE ID', 'Connected UPF', 'EMS Name', 'NE Address', 'NE Location (Latitude)', 'NE Location (Longitude)', 'Description']
    // flatMap으로 평탄화 작업
    let num = 0
    invalidData = rowData.flatMap((item, index) => {
      return Object.entries(item).flatMap(([key, { value, cell }]) => {
        // 필수 값 체크
        if(!requiredFields.includes(key)){
          // 양식에 맞지 않는 키값
          return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' };
        }
        if (requiredFields.includes(key) && useUtil.isNull(value)) {
          // 키는 존재하나 데이터가 없는 경우
          return { no: ++num, ROW_NUM: index + 2, headerName: key, value, cellNum: cell, errorMsg: '형식에러' };
        }
        return [];
      })
    })
    return invalidData
  }
  /**
   * 업로드 요청하기 - 각 부모컴포넌트로 이벤트 전달
   */
  function fnUploadProc(){
    emit('uploadProc', uploadParams.value)
  }
  function fnClearFile(e){
    //
    fnInit()
    isExcel.value = false
    setTimeout(() => {
      gridApi.value.showNoRowsOverlay(); 
    }, 1);
  }

  // Grid 데이터 바인딩
  function fnOnGridReady(params){
    gridApi.value = params.api
  }
  function fnMakeNeJson(){
    let jsonData = {
      '@DU': {
        'NE ID': [],
        'NE Type': [],
        'NE Version': [],
        'Release Version': [],
        'Network': [],
        'NE Name': [],
        'Administrative State': [],
        'gNB DU Name': [],
        'gNB DU Name': [],
        'Endpoint CU IP address': [],
        'Local Time Offset': [],
        'NE Serial Number': [],
        'FW Auto Fusing': [],
      },
      '@GNB_INFORMATION': {
        'NE ID': [],
        'gNB Num': [],
        'gNB ID': [],
        'gNB ID Length': [],
        'Operator Name': [],
        'Access Mode': [],
      },
      '@GNB_IP_INFORMATION': {
        'NE ID': [],
        'gNB Num': [],
        'IP Index': [],
        'Local IPv4 Address': [],
        'Local IPv6 Address': [],
        'Secondary Local IPv4 Address': [],
        'Secondary Local IPv6 Address': [],
      },
      '@SERVER_INFORMATION': {
        'NE ID': [],
        'CFM': [],
        'PSM': [],
        'Local IPv4 Address': [],
        'Local IPv6 Address': [],
        'Secondary Local IPv4 Address': [],
        'Secondary Local IPv6 Address': [],
      },
      '@PLMN_INFORMATION': {
        'NE ID': [],
        'gNB Num': [],
        'PLMN Index': [],
        'MCC': [],
        'MNC': [],
      },
      '@MAIN_BOARD_INFORMATION': {
        'NE ID': [],
        'Unit Type': [],
        'Unit ID': [],
        'Board Type': [],
      },
      '@CLOCK_INFORMATION': {
        'NE ID': [],
        'Clock Source ID': [],
        'Clock Source': [],
        'Priority Level': [],
        'Quality Level': [],
      },
      '@PTP_INFORMATION': {
        'NE ID': [],
        'IP Version': [],
        'First Master IP': [],
        'Second Master IP': [],
        'Clock Profile': [],
        'PTP Domain': [],
        'PTP Hybrid Mode': [],
      },
      '@DEBUG_PORT_INFORMATION': {
        'NE ID': [],
        'Port Switch': [],
      },
      '@PORT_INFORMATION': {
        'NE ID': [],
        'Port ID': [],
        'VR ID': [],
        'Port Administrative State': [],
        'Connect Type': [],
        'UDE Type': [],
        'MTU': [],
        'Speed Duplex': [],
        'Fec Mode': [],
      },
      '@VIRTUAL_ROUTING_INFORMATION': {
        'NE ID': [],
        'VR ID': [],
      },
      '@IP_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'External Interface Name': [],
        'IP Address': [],
        'IP Get Type': [],
        'Management': [],
        'Control': [],
        'Bearer': [],
        'IEEE1588': [],
      },
      '@VLAN_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'VLAN Interface Name': [],
        'VLAN ID': [],
        'VR ID': [],
      },
      '@LAG_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'LAG ID': [],
        'VR ID': [],
        'LAG Interface Name': [],
      },
      '@ROUTE_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'VR ID': [],
        'IP Prefix': [],
        'IP Prefix Length': [],
        'IP Gateway': [],
        'Route Interface Name': [],
      },
      '@SYSTEM_LOCATION_INFORMATION': {
        'NE ID': [],
        'User Defined Mode': [],
        'Latitude': [],
        'Longitude': [],
        'Height': [],
      },
      '@RU_INFORMATION': {
        'NE ID': [],
        'Start Frequency': [],
        'Second Start Frequency': [],
        'Uncertainty Semi Major': [],
        'Uncertainty Semi Minor': [],
        'Orientation Of Majjor Axis': [],
        'Uncertainty Altitude': [],
        'Confidence': []
      },
      '@IPSEC_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'VR ID': [],
        'Interface Name1': [],
        'Peer IP Version': [],
        'First Peer IP': [],
        'Second Peer IP': [],
        'Inner IP Version': [],
        'Tunnel Mode': [],
        'Interface Name2': [],
        'Interface Name3': [],
        'Interface Name4': [],
        'Interface Name5': [],
        'Interface Name6': [],
        'Crypto Algorithm': [],
        'Hash Algorithm': [],
        'Local ID Type': [],
        'Local ID': [],
      },
      '@PKI_INFORMATION': {
        'NE ID': [],
        'CPU ID': [],
        'IP Address': [],
        'FQDN': [],
        'Port': [],
        'Path': [],
        'DN': [],
        'DN Domain': [],
        'CA DN': [],
        'Hash Algorithm': [],
      },
    }

    return JSON.stringify(jsonData)
  }
</script>
