/*
 * @Descripttion: 导入通用代码
 * @Author: vian
 * @Date: 2020-09-09 10:45:54
 */
const XML_RN_STR = "@-@";
const INTERFACE_EXPORT_BASE = (() => {
  // xml字符实体
  const XMLEntity = {
    "&#x0020;": "escape{space}",
    "&#x20;": "escape{simpleSpace}",
    "&#x0009;": "escape{tab}",
    "&#x9;": "escape{simpleTab}",
    "&#x000D;": "escape{return}",
    "&#xD;": "escape{simpleReturn}",
    "&#000A;": "escape{newLine}",
    "&#xA;": "escape{simpleNewLine}",
    "&lt;": "escape{less}",
    "&gt;": "escape{greater}",
    "&amp;": "escape{and}",
    "&quot;": "escape{quot}",
    "&apos;": "escape{apos}",
  };

  // 避免字符实体进行转义。原文本中含有xml字符实体，转换为其他字符。
  function escapeXMLEntity(str) {
    for (const [key, value] of Object.entries(XMLEntity)) {
      str = str.replace(new RegExp(key, "g"), value);
    }
    return str;
  }
  // 将文本还原为字符实体
  function restoreXMLEntity(str) {
    for (const [key, value] of Object.entries(XMLEntity)) {
      str = str.replace(new RegExp(value, "g"), key);
    }
    return str;
  }

  /*
   * 根据字段数组获得所要字段的值 eg: 要获取标段下的单项工程： ['标段', '单项工程'];
   * 属性需要加前缀：“_”
   * 节点的不需要加前缀
   * @param {Object}source 源数据
   *        {Array}fields 字段数组
   * @return {String}
   * @example getValue(source, ['标段', '_文件类型'])
   * */
  function getValue(source, fields) {
    let cur = source;
    for (const field of fields) {
      if (!cur[field]) {
        return "";
      }
      cur = cur[field];
    }
    if (typeof cur === "string") {
      cur = cur.replace(new RegExp(XML_RN_STR, "g"), "\r\n");
    }
    return cur || "";
  }

  // 获取布尔型的数据
  function getBool(source, fields) {
    return getValue(source, fields) === "true" ? true : false;
  }

  // 获取数据类型
  function _plainType(v) {
    return Object.prototype.toString.call(v).slice(8, -1);
  }

  /*
   * 获取某字段的值，强制返回数组，防止一些错误。如果期待返回数组，可以用此方法。
   * @param {Object}source 数据源
   *        {Array}fields 取的字段
   * @return {Array}
   * @example arrayValue(source, ['标段', '单项工程'])
   * */
  function arrayValue(source, fields) {
    let target = getValue(source, fields);
    if (_plainType(target) === "Object") {
      target = [target];
    } else if (_plainType(target) !== "Array") {
      target = [];
    }
    return target;
  }

  // 获取费用
  function getFee(fees, fields) {
    if (!Array.isArray(fees) || !fees.length) {
      return "0";
    }
    const feeData = fees.find((fee) => fee.fieldName === fields[0]);
    return (feeData && feeData[fields[1]]) || "0";
  }

  // 合并价格
  function mergeFees(feesA, feesB) {
    if (!feesA) {
      return feesB;
    }
    if (!feesB) {
      return [];
    }
    feesB.forEach((feeB) => {
      const sameKindFee = feesA.find((feeA) => feeA.fieldName === feeB.fieldName);
      if (sameKindFee) {
        Object.assign(sameKindFee, feeB);
      } else {
        feesA.push(feeB);
      }
    });
    return feesA;
  }

  // 将A对象的属性赋值到B对象上
  function assignAttr(target, source, attrs) {
    if (!source || !target) {
      return;
    }
    const sourceAttrs = attrs || Object.keys(source);
    for (const attr of sourceAttrs) {
      // 如果值是undefined，则不进行赋值覆盖处理
      if (attr === "children" || source[attr] === undefined) {
        continue;
      }
      target[attr] =
        attr === "fees"
          ? mergeFees(target[attr], source[attr]) // 如果是价格，不能简单地覆盖，要合并两个对象的价格
          : source[attr];
    }
  }

  // 获取固定ID
  function getFlag(data) {
    return (data.flags && data.flags[0] && data.flags[0].flag) || 0;
  }

  // 设置成树结构数据
  function setTreeData(data, parent, next) {
    const defalutID = -1;
    data.ID = uuid.v1();
    data.ParentID = (parent && parent.ID) || defalutID;
    data.NextSiblingID = (next && next.ID) || defalutID;
  }

  // 递归设置树结构数据，并返回设置好的数据，递归items数组
  function mergeDataRecur(parent, items) {
    const rst = [];
    for (let i = 0; i < items.length; i++) {
      const cur = items[i];
      const next = items[i + 1];
      setTreeData(cur, parent, next);
      rst.push(cur);
      if (cur.items && cur.items.length) {
        rst.push(...mergeDataRecur(cur, cur.items));
      }
    }
    return rst;
  }

  // 递归获取相关数据，(同层可以出现不同节点)
  // fields内字段的顺序即决定了提取数据类型的顺序，如fields = [['gruop'], ['item']]，则提取的数据同层中group数据在item数据之前
  function extractItemsRecur(src, fields, extractFuc) {
    const rst = [];
    for (const field of fields) {
      const itemsSrc = arrayValue(src, field);
      if (itemsSrc.length) {
        /* const items = itemsSrc.map(itemSrc => {
          const obj = extractFuc(itemSrc, field[0]);
          obj.children = extractItemsRecur(itemSrc, fields, extractFuc);
          return obj;
        }); */
        const items = [];
        itemsSrc.forEach((itemSrc) => {
          const obj = extractFuc(itemSrc, field[0]);
          if (obj) {
            if (!obj.children || obj.children.length === 0) {
              obj.children = extractItemsRecur(itemSrc, fields, extractFuc);
            }
            items.push(obj);
          }
        });
        rst.push(...items);
      }
    }
    return rst;
  }

  const UTIL = Object.freeze({
    escapeXMLEntity,
    restoreXMLEntity,
    getValue,
    getBool,
    arrayValue,
    getFee,
    mergeFees,
    assignAttr,
    setTreeData,
    mergeDataRecur,
    getFlag,
    extractItemsRecur,
  });

  /**
   * 合并基本信息或工程特征
   * @param {Array} source - 提取的数据
   * @param {Array} target - 模板数据
   * @return {Array}
   */
  function mergeInfo(source, target) {
    source.forEach((item) => mergeChild(item, target));
    return target;

    function mergeChild(item, target) {
      for (const child of target) {
        if (child.key === item.key) {
          child.value = item.value;
          return true;
        }
        if (child.items && child.items.length) {
          const rst = mergeChild(item, child.items);
          if (rst) {
            return true;
          }
        }
      }
      return false;
    }
  }

  const { fixedFlag, BillType } = window.commonConstants;

  // 标题类别 - flag 映射
  const titleTypeToFlag = {
    1: fixedFlag.ONE_SEVEN_BILLS,
    2: fixedFlag.PROVISIONAL_TOTAL,
    3: fixedFlag.BILLS_TOTAL_WT_PROV,
    4: fixedFlag.DAYWORK_LABOR,
    5: fixedFlag.PROVISIONAL,
    6: fixedFlag.TOTAL_COST,
  };

  /**
   * 将提取出来的清单合并进清单模板
   * @param {Array} source - 从xml提取出来的清单
   * @param {Array} target - 清单模板数据
   * @param {Object} parent - 匹配到模板清单的父清单
   * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单，未匹配的模板清单不生成
   * @return {void}
   */
  function mergeBills(source, target, parent, matchedFlags) {
    source.forEach((bills, index) => {
      // 为了大项费用排序
      if (!parent) {
        bills.seq = index;
      }
      const simpleName = bills.name ? bills.name.replace(/\s/g, "") : "";
      const titleType = bills.titleType || "0";
      let matched;
      if (!parent) {
        if (titleTypeToFlag[titleType] === fixedFlag.ONE_SEVEN_BILLS || /100章.*章|100章.*700章|100章.*900章/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.ONE_SEVEN_BILLS);
        } else if (titleTypeToFlag[titleType] === fixedFlag.PROVISIONAL_TOTAL || /包含在清单合计中的材料、工程设备、专业工程暂估/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.PROVISIONAL_TOTAL);
        } else if (titleTypeToFlag[titleType] === fixedFlag.BILLS_TOTAL_WT_PROV || /清单合计减去材料、工程设备、专业工程暂估价/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.BILLS_TOTAL_WT_PROV);
        } else if (titleTypeToFlag[titleType] === fixedFlag.DAYWORK_LABOR || /计日工合计/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.DAYWORK_LABOR);
        } else if (titleTypeToFlag[titleType] === fixedFlag.PROVISIONAL || /暂列金额[(（]不含/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.PROVISIONAL);
        } else if (titleTypeToFlag[titleType] === fixedFlag.TOTAL_COST || /报价/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.TOTAL_COST);
        }
      } else {
        const parentSimpleName = parent.name ? parent.name.replace(/\s/g, "") : "";
        if (/100章.*章|100章.*700章|100章.*900章/.test(parentSimpleName) && /100章总则/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.ONE_HUNDRED_BILLS);
        } else if (/计日工合计/.test(parentSimpleName) && /劳务/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.LABOUR_SERVICE);
        } else if (/计日工合计/.test(parentSimpleName) && /材料/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.MATERIAL);
        } else if (/计日工合计/.test(parentSimpleName) && /机械/.test(simpleName)) {
          matched = target.find((bills) => getFlag(bills) === fixedFlag.CONSTRUCTION_MACHINE);
        }
      }
      if (matched) {
        matchedFlags.push(getFlag(matched));
        assignAttr(matched, bills);
        if (bills.children && bills.children.length) {
          mergeBills(bills.children, matched.children, matched, matchedFlags);
        }
      } else {
        target.push(bills);
      }
    });
  }

  /**
   * 处理清单
   * @param {Array} tenderBills - 从xml提取出来的清单
   * @param {Array} billsTarget - 拷贝一份的模板清单，用于合并提取清单
   * @param {Number} tenderID - 单位工程ID
   * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单，未匹配的模板清单不生成
   * @return {Array}
   */
  function handleBills(tenderBills, billsTarget, tenderID, onlyImportMatchBills) {
    const rst = [];
    // 将提取的清单数据合并进清单模板数据
    const matchedFlags = [];
    mergeBills(tenderBills, billsTarget, null, matchedFlags);

    // 如果只导入文件中有的清单，除100-700/900章清单、报价清单外，其他未匹配到的固定清单模板，均不导入
    function removeNotMatchFlagBills(bills) {
      const rst = [];
      bills.forEach((item) => {
        const flag = getFlag(item);
        if (!flag || [fixedFlag.ONE_SEVEN_BILLS, fixedFlag.TOTAL_COST].includes(flag) || matchedFlags.includes(flag)) {
          rst.push(item);
        }
        if (flag && ![fixedFlag.ONE_SEVEN_BILLS, fixedFlag.TOTAL_COST].includes(flag) && item.children && item.children.length) {
          item.children = removeNotMatchFlagBills(item.children);
        }
      });
      return rst;
    }
    if (onlyImportMatchBills) {
      billsTarget = removeNotMatchFlagBills(billsTarget);
    }

    // 大项费用按照清单标题排序
    billsTarget.sort((a, b) => a.seq - b.seq);
    // 给清单设置数据
    const rowCodeData = []; // 行号数据，用于转换行引用
    const toBeTransformBills = []; // 待转换的清单
    function setBills(bills, parentID) {
      bills.forEach((child, index) => {
        rst.push(child);
        child.projectID = tenderID;
        // 如果本身清单就有ID，那不用处理，可以减少处理清单模板的一些ID引用问题
        if (!child.ID) {
          child.ID = uuid.v1();
        }
        if (child.quantity) {
          child.quantityEXP = child.quantity;
        }
        child.ParentID = parentID;
        child.type = parentID === -1 ? BillType.DXFY : BillType.BILL;
        child.NextSiblingID = -1;
        const preChild = bills[index - 1];
        if (preChild) {
          preChild.NextSiblingID = child.ID;
        }
        if (child.rowCode) {
          const regStr = /{[^{}]+}/.test(child.rowCode) ? child.rowCode : `\\b${child.rowCode}\\b`;
          rowCodeData.push({ reg: new RegExp(regStr, "g"), ID: child.ID, rowCode: child.rowCode });
        }
        if (child.tempCalcBase) {
          toBeTransformBills.push(child);
        }
        if (child.children && child.children.length) {
          setBills(child.children, child.ID);
        }
      });
    }
    setBills(billsTarget, -1);
    // 转换计算基数
    toBeTransformBills.forEach((bills) => {
      rowCodeData.forEach(({ reg, ID, rowCode }) => {
        const rowCodeReg = new RegExp(`{${rowCode}}`);
        // 替换行号
        if (rowCodeReg.test(bills.tempCalcBase)) {
          bills.tempCalcBase = bills.tempCalcBase.replace(rowCodeReg, `@${ID}`);
        } else {
          bills.tempCalcBase = bills.tempCalcBase.replace(reg, `@${ID}`);
        }
        // 替换基数，防止其他公司的软件导出没有{}
        bills.tempCalcBase = bills.tempCalcBase.replace(/.?专项暂定合计.?/g, "{专项暂定合计}");
        bills.tempCalcBase = bills.tempCalcBase.replace(/.?各章清单合计.?/g, "{各章清单合计}");
      });

      /* 检查基数有效性，无效则使用模板的基数 */
      // 消除ID引用对基数分割的影响
      const IDs = bills.tempCalcBase.match(/@[\da-zA-Z-]{36}/g) || [];
      const bases = [...IDs];
      const str = bills.tempCalcBase.replace(/@[\da-zA-Z-]{36}/g, "");
      const otherBases = str.split(/[+-/*]/).filter((item) => !!item);
      bases.push(...otherBases);
      // 判定基数有效性
      const isValid = bases.every((base) => {
        if (base === "{专项暂定合计}" || base === "{各章清单合计}") {
          return true;
        }
        if (/^(\d+(\.\d+)?)%?$/.test(base)) {
          // 数值+%（可有可无）
          return true;
        }
        if (/[\da-zA-Z-]{36}/.test(base)) {
          // ID引用
          return true;
        }
        return false;
      });
      if (isValid) {
        bills.calcBase = bills.tempCalcBase;
      }
    });
    rst.forEach((bills) => delete bills.children);
    return rst;
  }

  function getTemplateBillsTarget(templateBills) {
    const templateTarget = _.cloneDeep(templateBills);
    const ungroupedData = [];
    function getBillsFromChildren(billsData) {
      for (const bills of billsData) {
        ungroupedData.push(bills);
        if (bills.children && bills.children.length) {
          getBillsFromChildren(bills.children);
        }
      }
    }
    getBillsFromChildren(templateTarget);
    BILLS_UTIL.resetTreeData(ungroupedData, uuid.v1, true);
    return templateTarget;
  }

  // 处理单位工程数据
  function handleTenderData(tenders, templateData, rationValuationData, engineeringLib, areaKey, onlyImportMatchBills) {
    tenders.forEach((tender, index) => {
      tender.compilation = compilationData._id;
      tender.userID = userID;
      tender.ID = templateData.projectBeginID + index + 1;
      tender.ParentID = templateData.projectBeginID;
      tender.NextSiblingID = index === tenders.length - 1 ? -1 : templateData.projectBeginID + index + 2;
      tender.projType = projectType.tender;
      const featureTarget = _.cloneDeep(templateData.feature); // 必须拷贝出一份新数据，否则会被下一个单位工程覆盖
      if (!engineeringLib) {
        throw "不存在可用工程专业。";
      }
      const taxData = engineeringLib.lib.tax_group[0];
      const featureSource = [
        ...(tender.feature || []),
        { key: "valuationType", value: "工程量清单" }, // 导入的时候以下项不一定有数据，但是需要自动生成
        { key: "feeStandard", value: engineeringLib.lib.feeName },
      ];
      const needEngineering = featureTarget && featureTarget.find((item) => item.key === "engineering");
      if (needEngineering) {
        featureSource.push({ key: "engineering", value: engineeringLib.lib.name });
      }
      tender.property = {
        areaKey,
        rootProjectID: tender.ParentID,
        region: "全省",
        engineering_id: engineeringLib.engineering_id,
        engineeringName: engineeringLib.lib.name,
        feeStandardName: engineeringLib.lib.feeName,
        engineering: engineeringLib.engineering,
        isInstall: engineeringLib.lib.isInstall,
        projectEngineering: engineeringLib.lib.projectEngineering,
        valuation: rationValuationData.id,
        valuationName: rationValuationData.name,
        valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
        boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
        taxType: taxData.taxType,
        projectFeature: mergeInfo(featureSource, featureTarget),
        featureLibID: (engineeringLib.lib.feature_lib[0] && engineeringLib.lib.feature_lib[0].id) || "",
        calcProgram: { name: taxData.program_lib.name, id: taxData.program_lib.id },
        colLibID: taxData.col_lib.id,
        templateLibID: taxData.template_lib.id,
        unitPriceFile: { name: tender.name, id: templateData.unitPriceFileBeginID + index }, // 新建单价文件
        feeFile: { name: tender.name, id: `newFeeRate@@${taxData.fee_lib.id}` }, // 新建费率文件
      };
      delete tender.feature;
      const tenderDataBills = getTemplateBillsTarget(templateData.bills);
      tender.bills = handleBills(tender.bills, tenderDataBills, tender.ID, onlyImportMatchBills); // 必须要拷贝一份，否则多单位工程情况下，前单位工程的清单数据会被后单位工程的覆盖
      // 给暂估材料和评标材料设置项目数据
      const setGLJRefFunc = (glj) => {
        glj.ID = uuid.v1();
        glj.projectID = tender.ID;
      };
      if (tender.evaluationList && tender.evaluationList.length) {
        tender.evaluationList.forEach(setGLJRefFunc);
      }
      if (tender.bidEvaluationList && tender.bidEvaluationList.length) {
        tender.bidEvaluationList.forEach(setGLJRefFunc);
      }
    });
  }

  /**
   * 将接口中提取出来数据转换成可入库的有效数据
   * 因为无法保证这一套逻辑能不能兼容以后的所有接口，因此提取数据与标准数据模板的合并放在前端进行。
   * 当统一逻辑无法满足某一接口时，接口可以根据标准模板数据自行进行相关处理。
   * @param {Object} importData - 各接口从xml提取出来的数据
   * @param {String} areaKey - 接口地区
   * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单，未匹配的模板清单不生成
   * @return {Promise<Object>}
   */
  async function handleImportData(importData, areaKey, onlyImportMatchBills) {
    const valuationID = compilationData.ration_valuation[0].id;
    if (!Array.isArray(importData.tenders) && !importData.tenders.length) {
      throw "导入的文件中不存在有效的标段数据。";
    }
    const projectCount = 1 + importData.tenders.length;
    // const feeName = compilationData.name === '安徽养护(2018)' ? '安徽养护' : '公路工程';
    // 一些接口需要根据导入文件，匹配工程专业库
    const rationValuationData = rationValuation && JSON.parse(rationValuation)[0]; // 只有工程量清单才能导入接口
    if (!rationValuationData) {
      throw "无法获取工程量清单计价数据";
    }
    const engineeringList = (rationValuationData.engineering_list || []).filter((item) => item.lib.visible);
    let engineeringLib = engineeringList[0];
    if (importData.engineeringName && importData.feeName) {
      const matchLibs = engineeringList.filter((item) => item.lib && item.lib.name === importData.engineeringName);
      engineeringLib = matchLibs.find((item) => item.lib.feeName === importData.feeName) || matchLibs[0] || engineeringList[0];
    }
    const engineeringID = engineeringLib.engineering_id || null;

    const templateData = await ajaxPost("/pm/api/getImportTemplateData", { user_id: userID, valuationID, engineeringID, projectCount });
    if (!templateData) {
      throw "无法获取有效模板数据。";
    }
    console.log(templateData);
    // 处理建设项目数据
    // 确定建设项目的名称（不允许重复）
    /* const sameDepthProjs = getProjs(projTreeObj.tree.selected);
    const matchedProject = sameDepthProjs.find(node => node.data.name === importData.name);
    if (matchedProject) {
      alert('test');
      importData.name += `(${moment(Date.now()).format('YYYY-MM-DD HH:mm:ss')})`;
    } */
    importData.compilation = compilationData._id;
    importData.userID = userID;
    importData.ID = templateData.projectBeginID;
    const { parentProjectID, preProjectID, nextProjectID } = projTreeObj.getRelProjectID(projTreeObj.tree.selected);
    importData.ParentID = parentProjectID;
    importData.preID = preProjectID;
    importData.NextSiblingID = nextProjectID;
    importData.projType = projectType.project;
    importData.property = {
      valuationType: commonConstants.ValuationType.BOQ, // 必为工程量清单
      boqType: commonConstants.BOQType.BID_SUBMISSION, // 导入后必为投标
      basicInformation: mergeInfo(importData.info, templateData.basicInfo), // 将提取的基本信息数据与标准基本信息数据进行合并（目前只赋值，没有匹配到的不追加）
    };
    delete importData.info;
    // 处理单位工程数据
    handleTenderData(importData.tenders, templateData, rationValuationData, engineeringLib, areaKey, onlyImportMatchBills);
    console.log(importData);
  }

  /*
   * 读取文件转换为utf-8编码的字符串
   * @param {Blob} file
   * @return {Promise}
   * */
  function readAsTextSync(file) {
    return new Promise((resolve, reject) => {
      const fr = new FileReader();
      fr.readAsText(file); // 默认utf-8，如果出现乱码，得看导入文件是什么编码
      fr.onload = function () {
        resolve(this.result);
      };
      fr.onerror = function () {
        reject("读取文件失败，请重试。");
      };
    });
  }

  /**
   *
   * @param {Function} entryFunc - 各导入接口提取导入数据方法
   * @param {File} file - 导入的文件
   * @param {String} areaKey - 地区标识，如：'安徽@马鞍山'
   * @param {Boolean} escape - 是否需要避免xml中的实体字符转换
   * @param {Boolean} onlyImportMatchBills - 是否只导入文件中的清单，未匹配的模板清单不生成
   * @return {Promise<Object>}
   */
  async function extractImportData(entryFunc, file, areaKey, escape = false, onlyImportMatchBills = false) {
    // 将二进制文件转换成字符串
    let xmlStr = await readAsTextSync(file);
    if (escape) {
      // x2js的str to json的实现方式基于DOMParser，DOMParser会自动将一些实体字符进行转换，比如 “&lt; to <”。如果不想进行自动转换，需要进行处理。
      xmlStr = escapeXMLEntity(xmlStr);
    }

    let reg1 = new RegExp(">\r\n", "g");
    xmlStr = xmlStr.replace(reg1, ">");

    let reg2 = new RegExp("\r\n", "g");
    xmlStr = xmlStr.replace(reg2, XML_RN_STR);

    // reg = /(?<=子目名称).+(?=单位)/g;
    // let matchResult = xmlStr.match(reg);
    // if (matchResult.length > 0) {
    //   console.log(matchResult[0]);
    // }

    // xmlStr = xmlStr.replace(new RegExp('\r\n', 'g'), XML_RN_STR);
    // 将xml格式良好的字符串转换成对象
    const x2js = new X2JS();
    let xmlObj = x2js.xml_str2json(xmlStr);
    xmlObj = JSON.parse(restoreXMLEntity(JSON.stringify(xmlObj)));
    console.log(xmlObj);
    if (!xmlObj) {
      throw "无有效数据。";
    }
    const importData = await entryFunc(areaKey, xmlObj);
    console.log(areaKey);
    await handleImportData(importData, areaKey, onlyImportMatchBills);
    return importData;
  }

  return {
    UTIL,
    extractImportData,
  };
})();
