/*
 * @Descripttion: 超高降效相关
 * @Author: Zhong
 * @Date: 2019-12-19 17:45:40
 */
/*
 * 整体逻辑为：
 * 1.一个请求中
 *   {删除}所有超高子目
 *   {更新}清单、定额下拉列文本
 *   {新增}清单、定额、定额人材机、项目人材机
 * 2.一个请求{重算被删除的定额清单节点、新增的定额节点
 */
const OVER_HEIGHT = (() => {
    // 选项类型，生成的超高子目所在位置
    const Option = {
        // 对应清单或分部下(默认)
        SEPARATION: 1,
        // 指定措施清单011704001
        MEASURE: 2,
        // 指定具体位置，显示分部分项以及措施项目的树结构显示叶子清单（分项）供勾选
        SPECIFIC: 3,
    };

    // 系数类别，类别不同，生成的定额人材机和算法不同
    const RateType = {
        LABOUR: 1,
        MACHINE: 2,
        LABOUR_MACHINE: 3,
    };

    // 系数类别对应的汇总定额金额字段(feeIndex里的字段)
    const FeeField = {
        [RateType.LABOUR]: 'labour',
        [RateType.MACHINE]: 'machine',
        [RateType.LABOUR_MACHINE]: 'material',
    };

    // 系数类别的定额人材机基础数据，不同系数类别，对应的人材机编码、名称等不同
    const BaseRatoinGLJ = {
        [RateType.LABOUR]: {
            code: '00010052',
            original_code: '00010052',
            name: '人工降效',
            type: gljType.LABOUR,
            shorName: '人',
        },
        [RateType.MACHINE]: {
            code: '99918004',
            original_code: '99918004',
            name: '机械降效',
            type: gljType.GENERAL_MACHINE,
            shorName: '机',
        },
        [RateType.LABOUR_MACHINE]: {
            code: '00010053',
            original_code: '00010053',
            name: '人工、机械降效增加费',
            type: gljType.GENERAL_MATERIAL,
            shorName: '材',
        },
    }
    // 选项二时的前九位清单编号
    const fixedCode = '011704001';
    const fixedCodeReg = new RegExp(`^${fixedCode}`);

    // 取费专业名称
    const programName = '超高降效';
    //const programName = '公共建筑工程'; // for Test

    // 指定清单表格
    let specificSpread = null;
    // 指定清单树
    let specificTree = null;
    // 指定清单窗口界面设置
    const specificTreeSetting = {
        emptyRowHeader: true,
        rowHeaderWidth: 15,
        treeCol: 0,
        emptyRows: 0,
        headRows: 1,
        headRowHeight: [40],
        defaultRowHeight: 21,
        cols: [{
            width: 140,
            readOnly: true,
            head: {
                titleNames: ["编码"],
                spanCols: [1],
                spanRows: [1],
                vAlign: [1],
                hAlign: [1],
                font: ["Arial"]
            },
            data: {
                field: "code",
                vAlign: 1,
                hAlign: 0,
                font: "Arial"
            }
        }, {
            width: 45,
            readOnly: true,
            head: {
                titleNames: ["类型"],
                spanCols: [1],
                spanRows: [1],
                vAlign: [1],
                hAlign: [1],
                font: ["Arial"]
            },
            data: {
                field: "subType",
                vAlign: 1,
                hAlign: 1,
                font: "Arial"
            }
        },
        {
            width: 205,
            readOnly: true,
            head: {
                titleNames: ["名称"],
                spanCols: [1],
                spanRows: [1],
                vAlign: [1],
                hAlign: [1],
                font: ["Arial"]
            },
            data: {
                field: "name",
                vAlign: 1,
                hAlign: 0,
                font: "Arial"
            }
        },
        {
            width: 60,
            readOnly: true,
            head: {
                titleNames: ["具体位置"],
                spanCols: [1],
                spanRows: [1],
                vAlign: [1],
                hAlign: [1],
                font: ["Arial"]
            },
            data: {
                field: "specific",
                vAlign: 1,
                hAlign: 1,
                font: "Arial"
            }
        },
        ]
    };

    // 源数据
    let sourceData;
    // 下拉项
    let comboData;
    // 初始化源数据和下拉项数据
    function init(source) {
        sourceData = source || [];
        comboData = sourceData
            ? sourceData
                .filter(item => !item.extra || !JSON.parse(item.extra))
                .map(item => item.name)
            : [];
    }
    // 获取下拉项
    function getComboData() {
        return comboData;
    }
    // 根据名称获取下拉项索引
    function getIndex(name) {
        return comboData.findIndex(item => item === name);
    }
    function getOverHeightItem(value) {
        return sourceData.find(item => item.name === value);
    }
    // 获取系数
    function getRate(overHeightItem) {
        return overHeightItem.labourRate
            || overHeightItem.machineRate
            || overHeightItem.labourMachineRate
            || null;
    }
    // 下拉项是否需要计算（生成子目）
    function isNeedToCalc(overHeightItem) {
        if (!overHeightItem) {
            return false;
        }
        const rate = getRate(overHeightItem);
        return !!rate;
    }
    // 是否是超高子目
    function isOverHeight(node) {
        return node
            && node.sourceType === ModuleNames.ration
            && node.data.type === rationType.overHeight;
    }
    // 获取超高降效列号
    function getOverHeightCol() {
        return projectObj.project.projSetting.main_tree_col.cols.findIndex(item => item.data.field === 'overHeight');
    }
    // 获取指定清单指定列
    function getSpecificCol() {
        return specificTreeSetting.cols.findIndex(item => item.data.field === 'specific');
    }
    // 获取触发动作：选项1、选项2、选项3，选项3时需要指定清单ID(specificID)
    function getAction() {
        return {
            option: projectObj.project.projectInfo.property.overHeightOption || Option.SEPARATION,
            specificID: projectObj.project.projectInfo.property.overHeightSpecificID || null,
        };
    }

    // 超高降效列的控制，右键计取触发
    function switchVisible(visible) {
        const curVisible = colSettingObj.getVisible('overHeight');
        if (curVisible === visible) {
            return;
        }
        colSettingObj.setVisible('overHeight', visible);
        colSettingObj.updateColSetting(true);
    }

    // 获取系数类型列表
    function getRateTypeList(overHeightItem) {
        const rst = [];
        if (commonUtil.isNumber(overHeightItem.labourRate)) {
            rst.push({ type: RateType.LABOUR, rate: overHeightItem.labourRate });
        }
        if (commonUtil.isNumber(overHeightItem.machineRate)) {
            rst.push({ type: RateType.MACHINE, rate: overHeightItem.machineRate });
        }
        if (commonUtil.isNumber(overHeightItem.labourMachineRate)) {
            rst.push({ type: RateType.LABOUR_MACHINE, rate: overHeightItem.labourMachineRate });
        }
        return rst;
    }

    // 有效化变化节点的值，若值为无效值（下拉项中不存在），则将变化节点的值设成原值
    function validateData(changedData) {
        changedData.forEach(item => {
            if (!comboData.includes(item.value)) {
                item.value = item.node.data.overHeight;
            }
        });
    }

    // 简化变化节点：由于子项值继承父项，且变更节点中可能存在父子关系，因此需要去除子项节点
    function simplifyData(changedData) {
        const rst = [];
        const nodes = changedData.map(item => item.node);
        changedData.forEach(item => {
            let parent = item.parent;
            // 父项不存在变化节点中才将此数据放入返回数组中
            while (parent) {
                if (nodes.includes(parent)) {
                    return;
                }
                parent = parent.parent;
            }
            rst.push(item);
        });
        return rst;
    }

    // 设置单元格文本，单元格文本数据为暂存数据，方便后续获取更新、新增数据，若后续操作失败，则可用节点数据恢复单元格文本内容。
    function setTexts(changedData) {
        const sheet = projectObj.mainController.sheet;
        const func = () => {
            const overHeightCol = getOverHeightCol();
            changedData.forEach(item => {
                // 子项值随父项
                const nodes = [item.node, ...item.node.getPosterity()];
                nodes.forEach(node => {
                    const row = node.serialNo();
                    // 单元格没被锁定才填写暂存值
                    const locked = sheet.getCell(row, overHeightCol).locked();
                    if (!locked) {
                        sheet.setText(row, overHeightCol, item.value)
                    }
                });
            });
        };
        TREE_SHEET_HELPER.massOperationSheet(sheet, func);
    }

    // 获取其他措施费项目底下固定的节点(011704001...): 选项二时
    function getMeasureFixedNode() {
        const otherMeasureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.OTHER_MEASURE_FEE);
        const measureChildren = otherMeasureNode.getPosterity();
        return measureChildren.find(node => node.data.code && fixedCodeReg.test(node.data.code));
    }

    // 获取指定清单节点：选项三时
    function getSpecificNode(specificID) {
        return specificID
            ? projectObj.project.mainTree.nodes[`id_${specificID}`]
            : null;
    }

    // 变更超高降效列的操作检验，若选项为2、3时，需检验指定清单是否还存在，不存在则取消操作和提示
    function checkAction(action) {
        const { option, specificID } = action;
        if (option === Option.SEPARATION) {
            return true;
        } else if (option === Option.MEASURE) {
            const isValid = !!getMeasureFixedNode();
            if (!isValid) {
                $('#overHeightMeasure').modal('show');
            }
            return isValid;
        } else if (option) {
            const isValid = !!getSpecificNode(specificID);
            if (!isValid) {
                $('#overHeightSpecific').modal('show');
            }
            return isValid;
        }
    }

    // 设置指定清单界面选择框
    function setCheckBox(sheet, specificNodes) {
        const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox();
        const specificCol = getSpecificCol();
        // 只有叶子节点和非计算基数节点才是选择框
        const func = () => {
            specificNodes.forEach((node, index) => {
                if (!node.children.length && !node.data.calcBase) {
                    sheet.getCell(index, specificCol).cellType(checkBox);
                }
            });
        };
        TREE_SHEET_HELPER.massOperationSheet(sheet, func);
    }

    // 初始化指定清单选择树
    function initSpecificTree(data, sheet, setting) {
        specificTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
        const controller = TREE_SHEET_CONTROLLER.createNew(specificTree, sheet, setting, false);
        specificTree.loadDatas(data);
        controller.showTreeData();
        sheet.setRowCount(data.length);
        setCheckBox(sheet, specificTree.items);
    }

    // 表格点击事件，只有checkbox单元格会触发这个方法
    function handleSpreadButtonClick(e, args) {
        const { sheet, row, col } = args;
        // 只能单选，清空其他单元格的值并设置当前值
        const func = () => {
            const rowCount = sheet.getRowCount();
            const oldValue = sheet.getValue(row, col);
            for (let row = 0; row < rowCount; row++) {
                sheet.setValue(row, col, '');
            }
            sheet.setValue(row, col, !oldValue);
        }
        TREE_SHEET_HELPER.massOperationSheet(sheet, func);
    }

    // 初始化表格
    function initWorkbook() {
        specificSpread = sheetCommonObj.createSpread($('#specificArea')[0], 1);
        specificSpread.options.allowUserDragDrop = false; // 不允许拖填充，影响点击
        sheetCommonObj.spreadDefaultStyle(specificSpread);
        specificSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, handleSpreadButtonClick);
        // 设置表头
        const sheet = specificSpread.getSheet(0);
        const headers = sheetCommonObj.getHeadersFromTreeSetting(specificTreeSetting);
        sheetCommonObj.setHeader(sheet, headers);
        return specificSpread;
    }

    // 从造价书界面筛选获取指定清单界面数据
    function getSpecificData() {
        // 只显示分部分项、措施项目的清单节点
        const billsNodes = projectObj.project.Bills.tree.items;
        const flags = [fixedFlag.SUB_ENGINERRING, fixedFlag.MEASURE];
        const billsData = flags.reduce((allData, flag) => {
            const fixedNode = billsNodes.find(node => node.getFlag() === flag);
            const posterity = fixedNode.getPosterity();
            const data = [fixedNode, ...posterity].map(node =>
                ({
                    ID: node.data.ID,
                    ParentID: node.data.ParentID,
                    NextSiblingID: node.data.NextSiblingID,
                    code: node.data.code,
                    subType: billText[node.data.type],
                    name: node.data.name,
                    calcBase: node.data.calcBase,
                }));
            allData.push(...data);
            return allData;
        }, []);
        return billsData;
    }

    // 设置指定ID选中
    function setSpecific(ID) {
        if (!ID) {
            return;
        }
        const sheet = specificSpread.getSheet(0);
        const nodes = specificTree.items;
        const index = nodes.findIndex(node => node.data.ID === ID);
        if (!~index) {
            return;
        }
        const col = getSpecificCol();
        sheet.setValue(index, col, true);
    }

    // 从表格中获取勾选的指定ID
    function getSpecificFromSheet() {
        const sheet = specificSpread.getSheet(0);
        const nodes = specificTree.items;
        const rowCount = sheet.getRowCount();
        const col = getSpecificCol();
        for (let row = 0; row < rowCount; row++) {
            const value = sheet.getValue(row, col);
            if (value) {
                return nodes[row].data.ID;
            }
        }
        return null;
    }

    // 初始化指定清单选择界面
    function initSpecificModal() {
        $('#specificArea').show();
        const spread = initWorkbook();
        const sheet = spread.getSheet(0);
        const data = getSpecificData();
        initSpecificTree(data, sheet, specificTreeSetting);
        const { specificID } = getAction();
        setSpecific(specificID);
    }

    // 隐藏指定清单选择界面
    function hideSpecificModal() {
        $('#specificArea').hide();
        if (specificSpread) {
            specificSpread.destroy();
            specificSpread = null;
        }
    }

    // 初始化子目生成方式选项设置窗口
    function initModal() {
        const { option, specificID } = getAction();
        $(`input[name=cgx][value=${option}]`).prop('checked', 'checked');
        $('#specificArea').hide();
        if (option === Option.SPECIFIC) {
            initSpecificModal(specificID);
        }
    }

    // 超高降效下拉项或选项是否改变了
    function isValueChanged() {
        const updateData = getUpdateData();
        return !!(updateData.bills.length || updateData.ration.length);
    }

    // 对比两个选项行为，获取更新选项数据
    function getUpdateProjectData(oldAction, newAction) {
        if (!oldAction || !newAction) {
            return {};
        }
        const optionDiff = oldAction.option !== newAction.option;
        const specificDiff = oldAction.specificID !== newAction.specificID;
        const updateData = {
            ID: projectObj.project.ID(),
            overHeightOption: newAction.option,
            overHeightSpecificID: newAction.specificID,
        };
        return optionDiff || specificDiff
            ? updateData
            : {};
    }

    /**
     * 获取更新数据：对比项目节点中超高降效的新旧值，新值为暂存的单元格文本，旧值为节点data数据
     * @param {Object} newAction - 选项行为
     * @return {Object} - {
     *                      project: {ID: Number，overHeightOp: Number, overHeightSpecificID: Number||Null}, 
     *                      bills: [{ID: Number, overHeight: String}], 
     *                      ration: [{ID: Number, overHeight: String}]
     *                    }
     */
    function getUpdateData(newAction) {
        const update = {
            project: {}, // 可能会更改项目属性的超高降效设置
            bills: [],
            ration: [],
        };
        const oldAction = getAction();
        const updateProjectData = getUpdateProjectData(oldAction, newAction);
        Object.assign(update.project, updateProjectData);
        const nodes = projectObj.project.mainTree.items;
        const sheet = projectObj.mainController.sheet;
        const overHeightCol = getOverHeightCol();
        nodes.forEach((node, index) => {
            const newValue = sheet.getText(index, overHeightCol);
            const oldValue = node.data.overHeight;
            // 非严等
            if (!commonUtil.similarEqual(newValue, oldValue)) {
                const type = node.sourceType === projectObj.project.Bills.getSourceType()
                    ? 'bills'
                    : 'ration';
                update[type].push({
                    ID: node.data.ID,
                    overHeight: newValue
                });
            }
        });
        return update;
    }

    /**
     * 获取删除数据：项目中所有超高子目
     * @return {Object} - {ration: [{ID: Number}]}
     */
    function getDeleteData() {
        const del = {
            ration: [],
        };
        const rations = projectObj.project.Ration.datas;
        del.ration = rations
            .filter(ration => ration.type === rationType.overHeight)
            .map(ration => ({ ID: ration.ID }));
        return del;
    }

    /**
     * 获取需要生成超高子目的定额节点
     * @return {Array} - [{node: Object, overHeight: String}]
     */
    function getNeedCalcRationItems() {
        // 从整个项目中筛选当前下拉项单元格的文本是需要计算的定额节点
        const nodes = projectObj.project.mainTree.items;
        const sheet = projectObj.mainController.sheet;
        const overHeightCol = getOverHeightCol();
        const rst = [];
        nodes.forEach(node => {
            // 非超高子目的定额节点才生成
            const notOverHeightRationNode = node.sourceType !== projectObj.project.Ration.getSourceType()
                || node.data.type === rationType.overHeight;
            if (notOverHeightRationNode) {
                return;
            }
            const overHeight = sheet.getText(node.serialNo(), overHeightCol);
            const overHeightItem = getOverHeightItem(overHeight);
            if (isNeedToCalc(overHeightItem)) {
                rst.push({ node, overHeight });
            }
        });
        return rst;
    }

    // 根据选项获取超高子目挂载的清单
    function getMountedBills(action) {
        const { option, specificID } = action;
        // 生成清单数据
        function initMountedBills() {
            // 生成的清单位置为其他措施项目的首项
            const measureNode = projectObj.project.mainTree.items.find(node => node.getFlag() === fixedFlag.OTHER_MEASURE_FEE);
            const firstNode = measureNode.children[0];
            //const parent = measureNode.children[measureNode.children.length - 1];
            // 具体完整数据需要在后端跟标准数据对比完善
            return {
                projectID: projectObj.project.ID(),
                billsLibId: +projectObj.project.projectInfo.engineeringInfo.bill_lib[0].id,
                ID: uuid.v1(),
                ParentID: measureNode.data.ID,
                NextSiblingID: firstNode ? firstNode.data.ID : -1,
                type: billType.BILL,
                code: fixedCode,
                name: '超高施工增加',
                unit: 'm2',
                quantity: '1',
            };
        }
        // 选项一
        if (option === Option.SEPARATION) {
            return {
                isNew: false,
                bills: null,
            };
        } else if (option === Option.MEASURE) {   // 选项二且造价书没有相关清单，需要插入清单
            const fixedNode = getMeasureFixedNode();
            return {
                isNew: !fixedNode,
                bills: fixedNode ? fixedNode.data : initMountedBills(),
            };
        } else {
            const specificNode = getSpecificNode(specificID);
            return {
                isNew: false,
                bills: specificNode.data,
            }
        }
    }

    // 获取清单节点下最末非超高子目定额
    function getLastNormalRationNode(billsNode) {
        const nodes = billsNode && billsNode.children
            ? billsNode.children.filter(node => node.data.type !== rationType.overHeight)
            : [];
        return nodes[nodes.length - 1]
    }

    // 将需要生成超高子目的定额数据按照挂载清单和超高名称进行分组，其下数组为关联的主定额
    // @return {Object} {'billsID@overHeight': [node: rationNode]}
    function getGroupData(rationItems, mountedBillsID) {
        // mapping: billsID-overHeightName
        const group = {};
        rationItems.forEach(item => {
            // 无指定的清单ID，则挂载在各自的清单上
            const billsID = mountedBillsID || item.node.data.billsItemID
            const key = `${billsID}@${item.overHeight}`;
            if (!group[key]) {
                group[key] = [];
            }
            group[key].push(item.node);
        });
        return group;
    }

    // 根据系数类型获取汇总的定额金额，这个金额作为计算定额人材机的基数
    function getFeeByRateType(rateType, referenceRations) {
        const feeField = FeeField[rateType];
        // 汇总定额节点相关综合合价
        return referenceRations.reduce((sum, rationNode) => {
            const feeObj = rationNode.data.feesIndex;
            const totalFee = feeObj && feeObj[feeField]
                ? feeObj[feeField].totalFee
                : 0;
            return scMathUtil.roundForObj(sum + totalFee, decimalObj.process);
        }, 0);
    }
    // 通过超高降效计算得来的定额人材机消耗量：消耗量=定额消耗量=算出来的值
    function getQuantity(rate, fee) {
        return String(scMathUtil.roundForObj(rate * fee, decimalObj.glj.quantity));
    }

    /**
     * 获取定额喝定额人材机数据 
     * @param {Array} rationItems - 需要生成超高子目的定额数据{node: Object, overHtight: String}
     * @param {Object} mountedBills - 挂载到的清单数据
     * @return {Object} - {ration: Array, rationGLJ: Array} 
     */
    function getAddRationAndRationGLJData(rationItems, mountedBills) {
        // 生成定额数据
        function initRation(bills, overHeightItem, serialNo) {
            const programID = projectObj.project.calcProgram.compiledTemplateMaps[programName];
            // 生成的超高子目消耗量为1
            const quantity = '1';
            // 含量为 定额消耗量 / 清单消耗量
            const tempV = quantity / bills.quantity;
            const contain = isFinite(tempV)
                ? scMathUtil.roundForObj(tempV, decimalObj.ration.quantity)
                : '0';
            return {
                projectID: projectObj.project.ID(),
                billsItemID: bills.ID,
                ID: uuid.v1(),
                programID,
                serialNo,
                code: overHeightItem.code,
                name: overHeightItem.name,
                unit: overHeightItem.unit,
                type: rationType.overHeight,
                quantity,
                contain,
            }
        }
        // 根据分组的keys获取定额serialNo值映射表 billsID@overHeight
        function getSerialNoMappig(groupKeys) {
            // 清单ID - serialNo映射
            const billsIDMap = {};
            // 完整的key - serialNo映射
            const keyMap = {};
            const nodes = projectObj.project.mainTree.nodes;
            // 先给根据清单ID设置上第一个serialNo值（基准值），和超高项数据
            groupKeys.forEach(key => {
                const [billsID, overHeight] = key.split('@');
                if (billsIDMap[billsID]) {
                    billsIDMap[billsID].items.push(overHeight);
                    return;
                }
                const billsNode = nodes[`id_${billsID}`];
                const lastNormalRationNode = billsNode ? getLastNormalRationNode(billsNode) : null;
                const serialNo = lastNormalRationNode ? lastNormalRationNode.data.serialNo + 1 : 1;
                billsIDMap[billsID] = { serialNo, items: [overHeight] };
            });
            // 将同一清单下的超高项按照下拉项位置排序
            Object.entries(billsIDMap).forEach(([billsID, { serialNo, items }]) => {
                items.sort((a, b) => getIndex(a) - getIndex(b));
                items.forEach((overHeight, index) => {
                    const key = `${billsID}@${overHeight}`;
                    keyMap[key] = serialNo + index;
                });
            });
            return keyMap;
        }
        // 生成超高子目的定额人材机数据，定额人材机的属性只是一部分，还有部分数据需要在后端处理
        function initRationGLJ(ration, typeItem, referenceRationNodes) {
            const { type, rate } = typeItem;
            const sumFee = getFeeByRateType(type, referenceRationNodes);
            // 不同类型的基础人材机属性
            const quantity = getQuantity(rate, sumFee);
            const baseObj = BaseRatoinGLJ[type];
            // 补全定额人材机属性，共性属性
            const extendObj = {
                projectID: projectObj.project.ID(),
                ID: uuid.v1(),
                billsItemID: ration.billsItemID,
                rationID: ration.ID,
                repositoryId: -1,
                GLJID: -1,
                unit: '%',
                specs: '',
                // 定额人材机没有价格字段，但是生成单价文件需要需要这两个价格字段，默认为“1”
                basePrice: '1',
                marketPrice: '1',
                quantity,
                rationItemQuantity: quantity,
            };
            return { ...baseObj, ...extendObj };
        }
        const add = {
            ration: [],
            rationGLJ: []
        };
        if (!rationItems.length) {
            return add;
        }
        const mountedBillsID = mountedBills
            ? mountedBills.ID
            : null;
        // 分析分组数据，获取定额及定额人材机数据
        const group = getGroupData(rationItems, mountedBillsID);
        const rationSerialNoMapping = getSerialNoMappig(Object.keys(group));
        // 获取定额及定额人材机数据
        Object.entries(group).forEach(([key, referenceRationNodes]) => {
            const [billsID, overHeight] = key.split('@');
            const overHeightItem = getOverHeightItem(overHeight);
            const bills = billsID === mountedBillsID
                ? mountedBills
                : projectObj.project.Bills.datas.find(item => item.ID === billsID);
            const serialNo = rationSerialNoMapping[key];
            const overHeightRation = initRation(bills, overHeightItem, serialNo);
            // 给生成的超高子目定额，设置关联定额列表（关联定额工程量等发生变化，需要重算超高子目）
            overHeightRation.referenceRationList = referenceRationNodes.map(node => node.data.ID);
            add.ration.push(overHeightRation);
            // 根据超高项获取系数列表，系数列表一个元素会根据系数类别生成一条定额人材机(人、机、材料)
            const rateTypeList = getRateTypeList(overHeightItem);
            const rationGLJs = rateTypeList.map(rateTypeItem => initRationGLJ(overHeightRation, rateTypeItem, referenceRationNodes));
            add.rationGLJ.push(...rationGLJs);
        });
        return add;
    }
    /**
     * 获取插入数据：超高子目(定额)、清单(选项2、3时可能会插入)
     * @param {Object} action - {option: Number, specificID: Undefined||Null||Number}
     * @return {Array} - {bills: Array, ration: Array, rationGLJ: Array}
     */
    function getAddData(action) {
        const add = {
            bills: [],
            ration: [],
            rationGLJ: [],
        };
        // 挂载到的清单，新增或已有的
        const mountedBills = getMountedBills(action);
        if (mountedBills.isNew) {
            add.bills.push(mountedBills.bills);
        }
        // 获取需要生成超高子目的定额数据
        const needCalcRationItems = getNeedCalcRationItems();
        const subData = getAddRationAndRationGLJData(needCalcRationItems, mountedBills.bills);
        add.ration = subData.ration;
        add.rationGLJ = subData.rationGLJ;
        return add;
    }

    /**
     * 生成传输数据
     * @param {Boolean} isCancelCalc - 是否取消计取 
     * @param {Object} action - 新的选项行为 
     * @return {Object}
     */
    function generatePostData(isCancelCalc, action) {
        // 默认模板
        const postData = {
            addData: {
                bills: [],
                ration: [],
                rationGLJ: [],
            },
            updateData: {
                project: {},
                bills: [],
                ration: [],
            },
            deleteData: {
                ration: [],
            },
        };
        // 取消计取费用，只删除超高子目
        if (isCancelCalc) {
            postData.deleteData = getDeleteData();
            postData.updateData = getUpdateData();
            return postData;
        }
        // 没有新的选项行为，获取当前项目的选项行为
        if (!action) {
            action = getAction();
        }
        const addData = getAddData(action);
        const updateData = getUpdateData(action);
        const deleteData = getDeleteData();
        return Object.assign(postData, { addData, updateData, deleteData });
    }

    /**
     * 更改了超高降效列(edited、rangeChanged)，触发事件
     * @param {Array} changedData - 变化的数据 [{node: Object, value: String}] 
     * @return {void} 
     */
    function handleValueChanged(changedData) {
        validateData(changedData);
        changedData = simplifyData(changedData);
        setTexts(changedData);
        const valuedChanged = isValueChanged();
        if (!valuedChanged) {
            return;
        }
        // 选项2、选项3情况下下拉可能会遇到，相关清单已经被删除，需要检测行为
        const action = getAction();
        const actionValid = checkAction(action);
        // actionValid为false的时候，可能后续需要恢复单元格文本值，根据后续用户在弹窗中的选择(后文ready事件中绑定)
        if (!actionValid) {
            return;
        }
        handleConfirmed();
    }

    // 确认事件
    async function handleConfirmed(isCancelCalc = false, action = null) {
        $.bootstrapLoading.start();
        try {
            const postData = generatePostData(isCancelCalc, action);
            const { addData, updateData, deleteData } = postData;
            // 更新、删除、新增数据
            // 返回的是新增的清单、定额人材机、项目人材机 rst = {bills: [], rationGLJ: [], projectGLJ: []}
            const rst = await ajaxPost('/project/calcOverHeightFee', postData);
            // 后续获取重算节点相关：
            // 新增的定额节点要在同步数据后才有，删除的定额节点在同步前找不到，因此同步前先获取被删除定额节点的清单ID列表
            const rationIDList = deleteData.ration.map(item => item.ID);
            let billsIDList = projectObj.project.Ration.datas
                .filter(item => rationIDList.includes(item.ID))
                .map(item => item.billsItemID);
            billsIDList = [...new Set(billsIDList)];
            // 同步数据
            const newAddData = { ...addData, ...rst };
            syncData(deleteData, updateData, newAddData);
            // 获取重算节点
            const reCalcNodes = getReCalcNodes(billsIDList, newAddData.ration);
            if (!reCalcNodes.length) {
                $.bootstrapLoading.end();
                return;
            }
            // 获取项目人材机数据，更新缓存
            $.bootstrapLoading.end(); // 重算节点相关方法里有loading，防止提前结束了loading
            // 重算相关节点
            projectObj.project.calcProgram.calcNodesAndSave(reCalcNodes);
        } catch (err) {
            alert(err);
            console.log(err);
            recoverCellsText();
            $.bootstrapLoading.end();
        }
    }

    /**
     * 获取需要重算的节点
     * @param {Array} billsIDList - 根据删除的定额ID数据获取的清单ID列表 
     * @param {Array} rationData - 新增的定额数据
     * @return {Array}
     */
    function getReCalcNodes(billsIDList, rationData) {
        // 获取被删除定额的清单节点
        const billsNodes = billsIDList.map(ID => projectObj.project.mainTree.nodes[`id_${ID}`]);
        // 获取新增的定额数据
        const rationNodes = rationData.map(ration => projectObj.project.mainTree.nodes[`id_${ration.ID}`]);
        return [...billsNodes, ...rationNodes];
    }

    /**
     * 同步、更新缓存的数据、节点
     * @param {Object} delData - 删除的数据，包含定额ID列表 
     * @param {Object} updateData - 更新的数据 
     * @param {Object} addData - 新增的完整清单和定额人材机数据，经过后端加工返回 
     * @return {void} 
     */
    function syncData(delData, updateData, addData) {
        // 被删除数据的清单ID@定额serialNo - 价格字段映射
        // 在新增节点时，给上原本该行的feesIndex字段，不然会出现新增节点价格为空，重算后价格有值，造成价格字段闪烁的情况
        // 新增节点赋上原本该行的feesIndex字段只是为了避免闪烁的情况。
        // 新节点fees数组没有赋值，后续计算的时候节点的价格字段会被初始化（calcTools.initFees中），因此这个操作不会对计算结果有影响
        function getSerialNoFeesIndexMapping({ ration }) {
            const mapping = {};
            const rationIDList = ration.map(item => item.ID);
            projectObj.project.Ration.datas
                .filter(item => rationIDList.includes(item.ID))
                .forEach(item => mapping[`${item.billsItemID}@${item.serialNo}`] = item.feesIndex);
            return mapping;
        }
        // 新增定额设置上暂时显示的价格字段
        function setTemporaryFeesIndex({ ration }, feesIndexMapping) {
            ration.forEach(item => {
                const key = `${item.billsItemID}@${item.serialNo}`;
                const oldFeesIndex = feesIndexMapping[key];
                if (oldFeesIndex) {
                    item.feesIndex = oldFeesIndex;
                }
            });
        }
        // 删除数据
        function del({ ration }) {
            const sheet = projectObj.mainController.sheet;
            const func = () => {
                // 删除定额数据、定额节点及子数据
                if (ration.length) {
                    const rationIDList = ration.map(item => item.ID);
                    projectObj.project.Ration.deleteDataSimply(rationIDList);
                    // 由于cacheTree delete方法会将preSelected设置成null
                    // 会导致变更焦点行时，清除不了原焦点行的选中色，因此这里重设下preSelected，在这里处理避免影响其他已有代码
                    projectObj.project.mainTree.preSelected = projectObj.project.mainTree.selected;
                }
            };
            TREE_SHEET_HELPER.massOperationSheet(sheet, func);
        }
        // 更新数据
        function update({ project, bills, ration }) {
            // 更新项目属性
            if (project) {
                const property = projectObj.project.projectInfo.property;
                Object.assign(property, project);
                // 更新节点（控制基数只读性，指定清单基数列只读）
                const specificNode = getSpecificNode(property.overHeightSpecificID);
                if (specificNode) {
                    TREE_SHEET_HELPER.refreshTreeNodeData(projectObj.mainController.setting, projectObj.mainController.sheet, [specificNode], false);
                }
            }
            const mainTree = projectObj.project.mainTree;
            // 更新节点超高降效
            [...bills, ...ration].forEach(({ ID, overHeight }) => {
                const node = mainTree.nodes[`id_${ID}`];
                if (node) {
                    node.data.overHeight = overHeight;
                }
            });
        }
        // 插入数据
        function add({ bills, ration, rationGLJ, projectGLJ }) {
            const sheet = projectObj.mainController.sheet;
            const func = () => {
                // 插入清单数据和清单节点主树节点
                if (bills.length) {
                    projectObj.project.Bills.addNewDataSimply(bills);
                }
                // 插入定额数据和定额节点
                if (ration.length) {
                    // 按照serialNo排序
                    ration.sort((a, b) => a.serialNo - b.serialNo);
                    projectObj.project.Ration.addNewDataSimply(ration);
                }
                // 插入定额人材机数据
                if (rationGLJ.length) {
                    projectObj.project.ration_glj.addDatasToList(rationGLJ);
                }
                // 插入项目人材机数据
                if (projectGLJ.length) {
                    projectObj.project.projectGLJ.loadNewProjectGLJToCaches(projectGLJ);
                    // 重算消耗量
                    projectObj.project.projectGLJ.calcQuantity();
                }
            };
            TREE_SHEET_HELPER.massOperationSheet(sheet, func);
        }

        const feesIndexMapping = getSerialNoFeesIndexMapping(delData);
        setTemporaryFeesIndex(addData, feesIndexMapping);
        del(delData);
        update(updateData);
        add(addData);
    }

    // 恢复暂存的单元格文本
    function recoverCellsText() {
        const sheet = projectObj.mainController.sheet;
        const func = () => {
            const nodes = projectObj.project.mainTree.items;
            const overHeightCol = getOverHeightCol();
            nodes.forEach((node, index) => {
                const newValue = sheet.getText(index, overHeightCol);
                const oldValue = node.data.overHeight;
                if (!commonUtil.similarEqual(newValue, oldValue)) {
                    sheet.setText(index, overHeightCol, oldValue);
                }
            });
        };
        TREE_SHEET_HELPER.massOperationSheet(sheet, func);
    }

    // 请空所有清单和定额的超高文本
    function clearOverHeightText() {
        const sheet = projectObj.mainController.sheet;
        const func = () => {
            const nodes = projectObj.project.mainTree.items;
            const overHeightCol = getOverHeightCol();
            nodes.forEach((node, index) => {
                if (node.data.overHeight) {
                    sheet.setText(index, overHeightCol, '');
                }
            });
        };
        TREE_SHEET_HELPER.massOperationSheet(sheet, func);
    }

    // 取消超高降效，删除所有超高子目
    function cancelCalc() {
        switchVisible(false);
        clearOverHeightText();
        handleConfirmed(true);
    }

    // 取消事件，触发了取消操作，需要恢复单元格文本
    function handleCancel() {
        $.bootstrapLoading.start();
        recoverCellsText();
        $.bootstrapLoading.end();
    }

    /**
     * 返回清单与超高子目和其定额人材机映射
     * @param {Array} rations - 全部超高子目数据 
     * @param {Array} rationGLJs - 全部超高子目定额人材机数据 
     * @return {Object} - {billsItemID@超高子目编码@定额人材机编码: {quanqity, rationItemQuantity}} 
     */
    function getRationGLJMap(rations, rationGLJs) {
        const mapping = {};
        rationGLJs.forEach(rGLJ => {
            const ration = rations.find(ration => ration.ID === rGLJ.rationID);
            const rationCode = ration ? ration.code : '';
            // 由于一个清单下不会存在两个相同编号的超高子目（相同编号会被自动汇总），因此这个key能确定唯一一条定额人材机
            const key = `${rGLJ.billsItemID}@${rationCode}@${rGLJ.original_code}`;
            mapping[key] = { quantity: rGLJ.quantity, rationItemQuantity: rGLJ.rationItemQuantity };
        });
        return mapping;
    }

    // 比较两个清单与超高子目和其定额人材机映射表，看是否相同
    function isMappingEqual(oldMapping, newMapping) {
        const oldKeys = Object.keys(oldMapping);
        const newKeys = Object.keys(newMapping);
        if (oldKeys.length !== newKeys.length) {
            return false;
        }
        const isEveryKeySame = oldKeys.every(key => {
            const oldData = oldMapping[key];
            const newData = newMapping[key];
            if (!newData) {
                return false;
            }
            const quantitySame = commonUtil.similarEqual(oldData.quantity, newData.quantity);
            const rationQuantitySame = commonUtil.similarEqual(oldData.rationItemQuantity, newData.rationItemQuantity);
            return quantitySame && rationQuantitySame;
        });
        return isEveryKeySame;
    }

    /**
     * 重新计取项目超高子目，超高子目的值与关联定额相关
     * 因此各种操作下改变了相关定额，都要重新计算超高子目
     * 为了降低复杂度和保证逻辑统一性，重新计取为重新走（删除新增逻辑）
     * 需要尽可能地降低操作的触发率
     */
    async function reCalcOverHeightFee() {
        const project = projectObj.project;
        // 如果项目没有超高降效数据，项目不可用超高降效，返回
        if (!project.isOverHeightProject()) {
            return;
        }
        // 如果没有超高定额，返回（因此删除了选项二三、指定的清单不会触发）
        const overHeightRations = project.Ration.datas.filter(ration => ration.type === rationType.overHeight);
        if (!overHeightRations.length) {
            return;
        }
        // 获取新旧超高数据映射表，不同才需要计算
        const overHeightRationIDs = overHeightRations.map(ration => ration.ID);
        const overHeigtRationGLJs = project.ration_glj.datas.filter(rGLJ => overHeightRationIDs.includes(rGLJ.rationID));
        const action = getAction();
        const { ration, rationGLJ } = getAddData(action);
        const oldMapping = getRationGLJMap(overHeightRations, overHeigtRationGLJs);
        const newMapping = getRationGLJMap(ration, rationGLJ);
        if (isMappingEqual(oldMapping, newMapping)) {
            return;
        }
        // 存在不同，重算
        await handleConfirmed();
    }

    // 事件监听
    $(document).ready(() => {
        // 设置窗口显示事件
        $('#overHeightOpt').on('shown.bs.modal', () => {
            initModal();
        });
        // 设置窗口隐藏事件
        $('#overHeightOpt').on('hide.bs.modal', () => {
            if (specificSpread) {
                specificSpread.destroy();
                specificSpread = null;
            }
            specificTree = null;
            // 指定清单时的选项，指定清单被删除，用户下拉改变超高降效列，
            // 弹出选中指定清单窗口，用户没有选定直接关闭窗口，此时需要把造价书暂存的值恢复
            handleCancel();
        });
        // 设置窗口单选变更事件
        $('input[name=cgx]').change(function () {
            const option = +$(this).val();
            switch (option) {
                case Option.SEPARATION:
                case Option.MEASURE:
                    hideSpecificModal();
                    break;
                case Option.SPECIFIC:
                    initSpecificModal();
                    break;
            }
        });
        // 设置窗口确认事件
        $('#overHeightOptConfirmed').click(() => {
            const option = +$('input[name=cgx]:checked').val();
            switch (option) {
                case Option.SEPARATION:
                    $('#overHeightOpt').modal('hide');
                    handleConfirmed(false, { option, specificID: null });
                    break;
                case Option.MEASURE:
                    const fixedNode = getMeasureFixedNode();
                    // 造价书不存在相关清单，提示是否新增清单，由提示窗口进行后续操作
                    if (!fixedNode) {
                        $('#overHeightMeasure').modal('show');
                        return;
                    }
                    // 存在相关清单
                    $('#overHeightOpt').modal('hide');
                    handleConfirmed(false, { option, specificID: null });
                    break;
                case Option.SPECIFIC:
                    const specificID = getSpecificFromSheet();
                    if (!specificID) {
                        alert('请指定清单');
                        return;
                    }
                    $('#overHeightOpt').modal('hide');
                    handleConfirmed(false, { option, specificID });
                    break;
            }
        });

        // 选项二下，改变超高降效的值，且措施项目下指定清单被删除，弹窗按钮事件
        $('#overHeightMeasureY').click(() => { // 确认 - 新增指定清单和相关数据
            const action = { option: Option.MEASURE, specificID: null };
            $('#overHeightOpt').modal('hide');
            $('#overHeightMeasure').modal('hide');
            handleConfirmed(false, action);
        });
        $('#overHeightMeasureN').click(handleCancel); // 取消

        // 选项三下，改变超高降效的值，且指定清单被删除，弹窗按钮事件
        $('#overHeightSpecificY').click(() => { // 确认 - 弹出设置窗口
            $('#overHeightOpt').modal('show');
        });
        $('#overHeightSpecificN').click(handleCancel); // 取消
    });

    return {
        init,
        getComboData,
        isOverHeight,
        switchVisible,
        handleValueChanged,
        cancelCalc,
        reCalcOverHeightFee,
    };
})();
