'use strict';

/**
 *
 *
 * @author Zhong
 * @date 2018/6/1
 * @version
 */
import mongoose from 'mongoose';
import CompilationModel from "../../users/models/compilation_model";
import moment from 'moment';
const uuidV1 = require('uuid/v1');
const billsLibModel = mongoose.model('std_bills_lib_list');
const billsGuideLibModel = mongoose.model('std_billsGuidance_lib');
const billsGuideItemsModel = mongoose.model('std_billsGuidance_items');
const stdBillsLibModel = mongoose.model('std_bills_lib_list');
const stdBillsModel = mongoose.model('std_bills_lib_bills');
const stdBillsJobsModel = mongoose.model('std_bills_lib_jobContent');
const stdRationModel = mongoose.model('std_ration_lib_ration_items');
const engLibModel = mongoose.model('engineering_lib');
const compilationModel = mongoose.model('compilation');
const billMaterialModel = mongoose.model('std_billsGuidance_materials');
const gljModel = mongoose.model('std_glj_lib_gljList');
const gljLibModel = mongoose.model('std_glj_lib_map');
const billClassModel = mongoose.model('billClass');
const idTree = require('../../../public/id_tree').getTree();
const _ = require('lodash');
const zhLibID = 'cf851660-3534-11ec-9641-2da8021b8e4e';
const cqLibID = '90c51220-a740-11e8-a354-ab5db7d42428';
module.exports = {
    handleCopyItems,
    getComBillsLibInfo,
    getBillsGuideLibs,
    initBillsGuideLib,
    updateBillsGuideLib,
    getLibWithBills,
    getItemsBybills,
    getItemsByBillIDs,
    updateItems,
    getBillMaterials,
    editBillMaterials,
    generateClassData,
    getClassExcelData,
    autoSetMaterial,
    testItems,
};

function setChildren(bill, parentMap) {
    let children = parentMap[bill.ID];
    if (children) {
        for (let c of children) {
            setChildren(c, parentMap);
        }
        bill.children = children;
    } else {
        bill.children = [];
    }
}

function sortChildren(lists) {
    let IDMap = {},
        nextMap = {},
        firstNode = null,
        newList = [];
    for (let l of lists) {
        if (l.children && l.children.length > 0) l.children = sortChildren(l.children); //递规排序
        IDMap[l.ID] = l;
        if (l.NextSiblingID != -1) nextMap[l.NextSiblingID] = l;
    }
    for (let t of lists) {
        if (!nextMap[t.ID]) { //如果在下一节点映射没找到，则是第一个节点
            firstNode = t;
            break;
        }
    }
    if (firstNode) {
        newList.push(firstNode);
        delete IDMap[firstNode.ID];
        setNext(firstNode, newList);
    }
    //容错处理，如果链断了的情况，直接添加到后面
    for (let key in IDMap) {
        if (IDMap[key]) newList.push(IDMap[key])
    }
    return newList;

    function setNext(node, array) {
        if (node.NextSiblingID != -1) {
            let next = IDMap[node.NextSiblingID];
            if (next) {
                array.push(next);
                delete IDMap[next.ID];
                setNext(next, array);
            }
        }
    }
}

function getItemFromChildren(children, allItems) {
    for (let i = 0; i < children.length; i++) {
        const cur = children[i];
        const next = children[i + 1];
        cur.NextSiblingID = next && next.ID || -1;
        allItems.push(cur);
        if (cur.children && cur.children.length) {
            getItemFromChildren(cur.children, allItems);
        }
    }
}

function resetTreeData(billsList) {
    const idMapping = {};
    idMapping['-1'] = -1;
    // 建立新ID-旧ID映射
    billsList.forEach(bills => idMapping[bills.ID] = uuidV1());
    billsList.forEach(function (bills) {
        bills.ID = idMapping[bills.ID] ? idMapping[bills.ID] : -1;
        bills.ParentID = idMapping[bills.ParentID] ? idMapping[bills.ParentID] : -1;
        bills.NextSiblingID = idMapping[bills.NextSiblingID] ? idMapping[bills.NextSiblingID] : -1;
    });
}

// 从某库中根据清单编号拷贝工艺
async function handleCopyItems(sourceGuideLibID, sourceBillsLibID, targetGuideLibID, targetBillsLibID) {
    const sourceBills = await stdBillsModel.find({ billsLibId: sourceBillsLibID, deleted: false }, '-_id ID code').lean();
    const targetBills = await stdBillsModel.find({ billsLibId: targetBillsLibID, deleted: false }, '-_id ID code').lean();
    const sourceItems = await billsGuideItemsModel.find({ libID: sourceGuideLibID, type: 0, deleted: false }, '-_id').lean(); // 过滤掉定额项
    const targetCodeIDMap = {};
    targetBills.forEach(bills => targetCodeIDMap[bills.code] = bills.ID);
    const toDeleteIDList = [];
    const insertBulks = [];
    sourceBills.forEach(bills => {
        const matchedItems = sourceItems.filter(item => item.billsID === bills.ID);
        // 重新将断缺的树结构数据整理好
        if (matchedItems.length) {
            const parentMap = {};
            matchedItems.forEach(item => {
                (parentMap[item.ParentID] || (parentMap[item.ParentID] = [])).push(item);
            });
            const roots = parentMap['-1'];
            roots.forEach(root => setChildren(root, parentMap));
            const sorted = sortChildren(roots);
            const newItems = [];
            getItemFromChildren(sorted, newItems);
            resetTreeData(newItems);
            const targetBillsID = targetCodeIDMap[bills.code];
            if (targetBillsID) {
                toDeleteIDList.push(targetBillsID);
                newItems.forEach(newItem => {
                    newItem.libID = targetGuideLibID;
                    newItem.billsID = targetBillsID;
                    insertBulks.push({
                        insertOne: { document: newItem }
                    });
                })
            }
        }
    });
    if (toDeleteIDList.length) {
        await billsGuideItemsModel.remove({ libID: targetGuideLibID, billsID: { $in: toDeleteIDList } });
    }
    if (insertBulks.length) {
        await billsGuideItemsModel.bulkWrite(insertBulks);
    }

}

async function getCompilationList() {
    let compilationModel = new CompilationModel();
    return await compilationModel.getCompilationList();
}

async function getComBillsLibInfo() {
    let rst = { compilationList: [], billsLibs: [] };
    let compilationList = await getCompilationList();
    if (compilationList.length <= 0) {
        throw '没有数据';
    }
    else {
        for (let compilation of compilationList) {
            rst.compilationList.push({ _id: compilation._id, name: compilation.name });
        }
        rst.billsLibs = await billsLibModel.find({ deleted: false }, '-_id billsLibId billsLibName');
        return rst;
    }
}

async function getBillsGuideLibs(findData, isTemporary) {
    if (isTemporary) {
        const libs = await billsGuideLibModel.find({ ID: { $in: [zhLibID, cqLibID] } }).lean();
        return libs;
    }
    return await billsGuideLibModel.find(findData);
}

// 如果是“重庆费用定额(2018)”，则默认叶子清单的项目指引窗口只有一条数据，取清单名称。
async function genGuidanceItems_cq18(guidanceLibId, billsLibId) {
    let bills = await stdBillsModel.find({ billsLibId: billsLibId, deleted: false, 'jobs.0': { $exists: true } });
    let insertArr = [];
    for (let bill of bills) {
        let newItem = {
            libID: guidanceLibId,
            ID: uuidV1(), ParentID: -1,
            NextSiblingID: -1,
            name: bill.name,
            type: 0,
            billsID: bill.ID
        };
        insertArr.push({ insertOne: { document: newItem } });
    }
    await billsGuideItemsModel.bulkWrite(insertArr);
}

//拷贝工作内容并转化为树结构，形成项目指引数据
async function genGuidanceItems(guidanceLibId, billsLibId) {
    let bills = await stdBillsModel.find({ billsLibId: billsLibId, deleted: false, 'jobs.0': { $exists: true } });
    //设置工作内容数据
    let jobIds = [];
    let totalJobs = [];
    for (let bill of bills) {
        for (let job of bill.jobs) {
            jobIds.push(job.id);
        }
    }
    jobIds = Array.from(new Set(jobIds));
    if (jobIds.length > 0) {
        totalJobs = await stdBillsJobsModel.find({ deleted: false, id: { $in: jobIds } });
    }
    if (totalJobs.length > 0) {
        let jobIdIndex = {};//id索引
        for (let job of totalJobs) {
            jobIdIndex[job.id] = job;
        }
        let insertArr = [];
        for (let bill of bills) {
            //排序后根据serialNo转换成NextSiblingID,倒序
            bill.jobs.sort(function (a, b) {
                let rst = 0;
                if (a.serialNo > b.serialNo) {
                    rst = -1;
                }
                else if (a.serialNo < b.serialNo) {
                    rst = 1;
                }
                return rst;
            });
            let jobNoIndex = {};//下标索引
            for (let i = 0; i < bill.jobs.length; i++) {
                let newItem = {
                    libID: guidanceLibId, ID: uuidV1(), ParentID: -1, NextSiblingID: jobNoIndex[i - 1] ? jobNoIndex[i - 1]['ID'] : -1,
                    name: jobIdIndex[bill.jobs[i]['id']]['content'], type: 0, billsID: bill.ID
                };
                jobNoIndex[i] = newItem;
                insertArr.push({ insertOne: { document: newItem } });
            }
        }
        await billsGuideItemsModel.bulkWrite(insertArr);
    }
}

async function initBillsGuideLib(updateData) {
    await billsGuideLibModel.create(updateData);
    let compilation = await compilationModel.findOne({ _id: mongoose.Types.ObjectId(updateData.compilationId) });
    if (compilation &&
        compilation.overWriteUrl &&
        compilation.overWriteUrl === '/web/over_write/js/chongqing_2018.js') {
        await genGuidanceItems_cq18(updateData.ID, updateData.billsLibId);
    } else {
        await genGuidanceItems(updateData.ID, updateData.billsLibId);
    }
}

async function updateBillsGuideLib(data) {
    if (data.updateType === 'delete') {
        //删除所有条目
        await billsGuideLibModel.remove(data.findData);
        await billsGuideItemsModel.remove({ libID: data.findData.ID });
    }
    else {
        await billsGuideLibModel.update(data.findData, { $set: data.updateData });
        await engLibModel.update({ 'billsGuidance_lib.id': data.findData.ID }, { $set: { 'billsGuidance_lib.$.name': data.updateData.name } }, { multi: true });
    }
}

async function getLibWithBills(libID) {
    let guidanceLib = await getBillsGuideLibs({ ID: libID });
    if (guidanceLib.length === 0) {
        throw '不存在此指引库！';
    }
    let billsLib = await stdBillsLibModel.findOne({ billsLibId: guidanceLib[0].billsLibId });
    if (!billsLib) {
        throw '引用的清单规则库不存在！';
    }
    let bills = await stdBillsModel.find({ billsLibId: billsLib.billsLibId }, '-_id code name ID unit NextSiblingID ParentID jobs items comment').lean();
    const guideItems = await billsGuideItemsModel.find({ libID: guidanceLib[0].ID }, '-_id billsID').lean();
    const billsMap = {};
    for (const item of guideItems) {
        billsMap[item.billsID] = true;
    }
    for (const item of bills) {
        if (billsMap[item.ID]) {
            item.hasGuide = true;
        }
    }

    return { guidanceLib: guidanceLib[0], bills };
}

function getAttrs(field, datas) {
    let rst = [];
    for (let data of datas) {
        if (data[field]) {
            rst.push(data[field]);
        }
    }
    return rst;
}

//定额项目指所引用定额是否被删除
function rationAllExist(rationItems, stdRationIdx) {
    for (let item of rationItems) {
        if (!stdRationIdx[item.rationID]) {
            return false;
        }
    }
    return true;
}

//将同层树结构转为顺序数组
function chainToArr(nodes) {
    let rst = [];
    let tempIdx = {};
    let nodeIdx = {};
    //建索引
    for (let node of nodes) {
        tempIdx[node.ID] = { ID: node.ID, NextSiblingID: node.NextSiblingID, preSibling: null, nextSibling: null };
        nodeIdx[node.ID] = node;
    }
    //建链
    for (let i in tempIdx) {
        let temp = tempIdx[i];
        if (temp.NextSiblingID != -1) {
            let next = tempIdx[temp.NextSiblingID];
            temp.nextSibling = next;
            next.preSibling = temp;
        }
    }
    let firstNode = null;
    for (let i in tempIdx) {
        if (!tempIdx[i].preSibling) {
            firstNode = tempIdx[i];
            break;
        }
    }
    //获得顺序队列
    while (firstNode) {
        rst.push(nodeIdx[firstNode.ID]);
        firstNode = firstNode.nextSibling;
    }
    return rst;
}

async function checkItems(items) {
    const type = { job: 0, ration: 1 };
    let rationItems = _.filter(items, { type: type.ration });
    let rationIds = getAttrs('rationID', rationItems);
    let stdRations = await stdRationModel.find({ ID: { $in: rationIds }, $or: [{ isDeleted: null }, { isDeleted: false }] });
    let stdRationIndex = {};
    for (let stdRation of stdRations) {
        stdRationIndex[stdRation.ID] = stdRation;
    }
    //判断定额完整性
    if (!rationAllExist(rationItems, stdRationIndex)) {
        //建定额链, 排序后再清除不存在的定额，保证顺序正确性
        rationItems = chainToArr(rationItems);
        //清除已被删除的定额
        let removeIds = [];
        _.remove(rationItems, function (item) {
            if (!stdRationIndex[item.rationID]) {
                removeIds.push(item.ID);
                return true;
            }
            return false;
        });
        _.remove(items, function (item) {
            return removeIds.includes(item.ID);
        });
        await billsGuideItemsModel.remove({ ID: { $in: removeIds } });
        //重组树结构
        let bulkArr = [];
        for (let i = 0, len = rationItems.length; i < len; i++) {
            rationItems[i].NextSiblingID = rationItems[i + 1] ? rationItems[i + 1].ID : -1;
            bulkArr.push({ updateOne: { filter: { ID: rationItems[i].ID }, update: { $set: { NextSiblingID: rationItems[i].NextSiblingID } } } });
        }
        await billsGuideItemsModel.bulkWrite(bulkArr);
    }
}

async function getItemsBybills(guidanceLibID, billsID) {
    let items = await billsGuideItemsModel.find({ libID: guidanceLibID, billsID: billsID});
    await checkItems(items);
    return items;
}

async function getItemsByBillIDs(guidanceLibID, billIDs) {
    let items = await billsGuideItemsModel.find({ libID: guidanceLibID, billsID: { $in: billIDs }});
    await checkItems(items);
    return items;
}

async function updateItems(updateDatas) {
    let bulkArr = [];
    for (let updateData of updateDatas) {
        if (updateData.updateType === 'create') {
            bulkArr.push({ insertOne: { document: updateData.updateData } });
        }
        else if (updateData.updateType === 'update') {
            bulkArr.push({ updateOne: { filter: updateData.findData, update: { $set: updateData.updateData } } });
        }
        else {
            bulkArr.push({ deleteOne: { filter: updateData.findData } });
        }
    }
    if (bulkArr.length > 0) {
        await billsGuideItemsModel.bulkWrite(bulkArr);
    }
}

// 获取清单材料数据
async function getBillMaterials(libID, billID) {
    // 指引下已有定额人材机（材料大类）
    let allGljList = [];
    const rationItems = await billsGuideItemsModel.find({ libID, billsID: billID, rationID: { $ne: null } }, '-_id rationID').lean();
    if (rationItems.length) {
        const rationIDs = rationItems.map(item => item.rationID);
        const rations = await stdRationModel.find({ ID: { $in: rationIDs } }, '-_id rationGljList');
        const gljIDs = [];
        rations.forEach(ration => {
            if (ration.rationGljList && ration.rationGljList.length) {
                gljIDs.push(...ration.rationGljList.map(rGlj => rGlj.gljId));
            }
        });
        if (gljIDs.length) {
            allGljList = await gljModel.find({ ID: { $in: [...new Set(gljIDs)] } }, '-_id ID code name specs gljType').lean();
            allGljList = allGljList.filter(glj => /^2/.test(glj.gljType) || [4, 5].includes(glj.gljType));
        }
    }
    // 清单材料
    let billMaterials = [];
    const billMaterial = await billMaterialModel.findOne({ libID, billID }).lean();
    if (!billMaterial || !billMaterial.materials) {
        return {
            billMaterials,
            allGljList
        };
    }
    const gljIDs = billMaterial.materials.map(m => m.gljID);
    const gljList = await gljModel.find({ ID: { $in: gljIDs } }, '-_id ID code name specs').lean();
    billMaterials = gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
    return {
        billMaterials,
        allGljList
    }
}

// 编辑清单材料数据，返回清单材料数据
async function editBillMaterials(libID, billID, gljCodes, compilationID) {
    const gljLib = await gljLibModel.findOne({ compilationId: compilationID }, '-_id ID').lean();
    const gljList = await gljModel.find({ repositoryId: gljLib.ID, code: { $in: gljCodes }, }, '-_id ID code name specs').lean();
    const gljMap = {};
    const materials = [];
    gljList.forEach(glj => {
        materials.push({ gljID: glj.ID });
        gljMap[glj.code] = 1;
    });
    const missCodes = gljCodes.filter(code => !gljMap[code]);
    if (missCodes.length) {
        throw new Error(`没有找到人材机:<br/>${missCodes.join('<br/>')}`);
    }
    const billMaterial = await billMaterialModel.findOne({ libID, billID }, '-_id ID').lean();
    if (billMaterial) {
        await billMaterialModel.update({ ID: billMaterial.ID }, { $set: { materials } });
    } else {
        await billMaterialModel.create({ libID, billID, ID: uuidV1(), materials });
    }
    return gljList.map(glj => ({ gljID: glj.ID, code: glj.code, name: glj.name, specs: glj.specs }));
}

// 是否为工序行
function isProcessNode(node) {
    return node && node.depth() % 2 === 0 && node.data.type === 0
}

// 是否是选项行
function isOptionNode(node) {
    return node && node.depth() % 2 === 1 && node.data.type === 0
}

// 判断蓝色子项和孙子项是否打勾了必填
function hasRequireData(node) {
    const requiredList = node.getPosterity().filter(subNode =>
        subNode.data.required === true
    );
    if (requiredList.length) { return true }
    return false;
}

// 这里判断有无无定额的情况，有的就返回true，没有就返回false
function isAllRationData(node) {
    let isRequired = true;
    if (node.children && node.children.length) {
        node.children.forEach(subNode => {
            if (!subNode.children || !subNode.children.length) {
                isRequired = false;
            }
        })
    }
    //这里是兼容第一层直接是定额的白色节点
    if (node.children && node.children.length) {
        node.children.forEach(subNode => {
            if (subNode.data.rationID) {
                isRequired = true;
            }
        })
    }
    return isRequired;
}

// 获取选套定额
function getOptionalData(node, list = []) {
    if (isProcessNode(node)) {
        node.children.forEach(element => {
            if (element.children && element.children.length) {
                element.children.forEach(item => {
                    if (item.data.rationID) {
                        list.push(item.data.rationID);
                    } else if (isProcessNode(item)) {
                        getOptionalData(item, list)
                    }
                })
            }
        });
    } else {
        node.children.forEach(element => {
            if (element.data.rationID) {
                list.push(element.data.rationID);
            }
        });
    }
    return list;
}

  // 获取必填项下的ID和name的键值对
  function getClassCodeStrData(nodes,data={classGroups:{},keyGroup:{}}){
    nodes.forEach(node=>{
       if (isProcessNode(node)&&node.data.required) {
           node.children.forEach(subNode=>{
               data.classGroups[subNode.data.ID]=subNode.data.name;  
               data.keyGroup[subNode.data.ID]=`${subNode.parent.data.name}:${subNode.data.name}`; 
           })
       }
       getClassCodeStrData(node.children,data);
    })
   return data;
}


//获取定额数据
    // requireRationData必套定额对象
    // optionalRationData 选逃定额对象
    // classGroups classCode文字和id键值对
    // classCodeList 各个classCode的pID和ID的关系
function getItemData(nodes, requireRationData = {}, optionalRationData = {}, classGroups = {}, prefixID = '', prefixSonID = '', IDData = {}, keyGroup={}) {
    const processNodes = nodes.filter(node => isProcessNode(node));
    // const classGroups = []; // 同层必填选项的数组（二维数组）
    processNodes.forEach(processNode => {
        // 蓝色节点，必填
        if (processNode.data.required) {
            // 白色节点
            const optionNodes = processNode.children.filter(node => isOptionNode(node));
            optionNodes.forEach(optionNode => {
                if (!requireRationData[optionNode.data.ID]) requireRationData[optionNode.data.ID] = [];
                if (!optionalRationData[optionNode.data.ID]) optionalRationData[optionNode.data.ID] = [];
                //白色节点下没有蓝色节点，就是到底了
                if (!optionNode.children.some(subOptionNode => isProcessNode(subOptionNode))) {
                    // 是否必套定额
                    if (isAllRationData(optionNode)) {
                        optionNode.children.forEach(subOptionNode => {
                            requireRationData[optionNode.data.ID].push([subOptionNode.data.rationID]);
                        })
                    } else {
                        optionalRationData[optionNode.data.ID] = getOptionalData(optionNode);
                    }
                    const kV = {};
                    kV[optionNode.data.ID] = optionNode.data.name;
                    Object.assign(classGroups, kV);

                    const keyData={};
                    keyData[optionNode.data.ID] = `${optionNode.parent.data.name}:${optionNode.data.name}`;
                    Object.assign(keyGroup,keyData);
                } else {
                    const kV = {};
                    kV[optionNode.data.ID] = optionNode.data.name;
                    Object.assign(classGroups, kV);

                    const keyData={};
                    keyData[optionNode.data.ID] = `${optionNode.parent.data.name}:${optionNode.data.name}`;
                    Object.assign(keyGroup,keyData);
                    // 后代项是否有必填
                    if (hasRequireData(optionNode)) {
                        //后代项有必填
                        prefixSonID = '';
                        getItemData(optionNode.children, requireRationData, optionalRationData, classGroups, optionNode.data.ID, prefixSonID, IDData, keyGroup);
                    } else {
                        //后代项无必填
                        optionNode.children.forEach(subOptionNode => {
                            // 是否必套定额
                            if (isAllRationData(optionNode)) {
                                if (!requireRationData[subOptionNode.parent.data.ID]) requireRationData[subOptionNode.parent.data.ID] = [];
                                requireRationData[subOptionNode.parent.data.ID].push(getOptionalData(subOptionNode));
                            } else {
                                if (!optionalRationData[subOptionNode.parent.data.ID]) optionalRationData[subOptionNode.parent.data.ID] = [];
                                optionalRationData[subOptionNode.parent.data.ID].push(...getOptionalData(subOptionNode));
                            }
                        })
                    }
                }
            })
        } else {
            // 蓝色节点，非必填
            if (hasRequireData(processNode)) {
                //后代项有必填
                if (isProcessNode(processNode)) {
                    //蓝色
                    // 是否必套定额
                    if (isAllRationData(processNode)) {
                        processNode.children.forEach((subProcessNode) => {
                            subProcessNode.children.forEach((sSubProcessNode) => {
                                //这里是特殊处理，因为原来的逻辑是直接把定额绑到必填白色选项中下面的，每个蓝色的一组，但是这样是不对的，需要绑定在必填白色选项下的蓝色节点，所以这里就需要传入蓝色节点的id
                                const requireChildrenID = sSubProcessNode.parent.data.required ? prefixSonID : sSubProcessNode.data.ID;
                                IDData[sSubProcessNode.data.ID] = prefixID;
                                getItemData(
                                    [sSubProcessNode],
                                    requireRationData,
                                    optionalRationData,
                                    classGroups,
                                    prefixID,
                                    requireChildrenID,
                                    IDData,
                                    keyGroup

                                )
                            })
                        })
                    } else {
                       // 全部选套就不用走循环了，直接按照选套执行
                       let key = processNode.data.ID;
                       if (prefixID) key = prefixID;
                       if (prefixSonID) key = prefixSonID;
                       if (!optionalRationData[key]) optionalRationData[key] = [];
                       optionalRationData[key].push(...getOptionalData(processNode));
                       // 因为这里没有按照走整个流程，所以文字和ID的关系需要获取补充
                      if(hasRequireData(processNode)){
                        const result =getClassCodeStrData(processNode.children)
                        Object.assign(classGroups,result.classGroups);
                        Object.assign(keyGroup,result.keyGroup);
                      }  
                    }
                }
            } else {
                let key = processNode.data.ID;
                if (prefixID) key = prefixID;
                if (prefixSonID) key = prefixSonID;
                // 是否必套定额
                if (isAllRationData(processNode)) {
                    if (!requireRationData[key]) requireRationData[key] = [];
                    requireRationData[key].push(getOptionalData(processNode));
                } else {
                    if (!optionalRationData[key]) optionalRationData[key] = [];
                    optionalRationData[key].push(...getOptionalData(processNode));
                }
            }
        }
    })
    return { requireRationData, optionalRationData, classGroups, IDData, keyGroup}
}


// 从指引节点，获取分类特征、必套定额数据
function getItemClassData(nodes, prefix,
    prefixID) {
    const processNodes = nodes.filter(node => isProcessNode(node));
    const classGroups = []; // 同层必填选项的数组（二维数组）
    processNodes.forEach(processNode => {
        const classItems = [];
        const optionNodes = processNode.children.filter(node => isOptionNode(node));
        optionNodes.forEach(optionNode => {
            // const name = prefix ? `${prefix}@${optionNode.data.name}` : optionNode.data.name;
            if (optionNode.parent && optionNode.parent.data.required && (!optionNode.children
                || !optionNode.children.length
                || (optionNode.children[0].data && optionNode.children[0].data.rationID)
                || !optionNode.children.some(node => isProcessNode(node)))) {

                // 必套定额
                classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID });

            } else {
                // classItems.push(...getItemCharacterData(optionNode.children, optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : ''));
                const childrenClassItem = getItemClassData(
                    optionNode.children,
                    optionNode.parent && optionNode.parent.data.required ? optionNode.data.name : '',
                    optionNode.parent && optionNode.parent.data.required ? optionNode.data.ID : ''
                );


                //如果返回的子项为空，但是父项又勾选了必填，则要把本身存入数组
                if (optionNode.parent
                    && optionNode.parent.data.required
                    && childrenClassItem.length === 0
                ) {
                    classItems.push({ name: optionNode.data.name, ID: optionNode.data.ID });
                } else {

                    classItems.push(...childrenClassItem);
                }
            }
        });
        if (classItems.length) {
            classGroups.push(classItems);
        }
    });


    // 拼接上一文本
    if (classGroups[0] && classGroups[0].length) {
        // classGroups[0] = classGroups[0].map(name => prefix ? `${prefix}@${name}` : name);
        classGroups[0] = classGroups[0].map(item => {
            item.name = prefix ? `${prefix}@${item.name}` : item.name
            item.ID = prefixID ? `${prefixID}@${item.ID}` : item.ID
            return item;
        });
    }
    // 二维数组内容排列组合
    while (classGroups.length > 1) {
        const prevClassItems = classGroups[0];
        const nextClassItems = classGroups[1];
        const mergedClassItems = [];
        for (let i = 0; i < prevClassItems.length; i++) {
            for (let j = 0; j < nextClassItems.length; j++) {
                // 拼接文本
                const mergedName = `${prevClassItems[i].name}@${nextClassItems[j].name}`;
                const mergedID = `${prevClassItems[i].ID}@${nextClassItems[j].ID}`;
                mergedClassItems.push({ name: mergedName, ID: mergedID });
            }
        }
        classGroups.splice(0, 2, mergedClassItems);
    }
    // 去重（类别别名要唯一）
    const items = classGroups[0] || [];
    const nameMap = {};
    const rst = [];
    items.forEach(item => {
        if (!nameMap[item.name]) {
            rst.push(item);
        }
        nameMap[item.name] = true;
    });
    return rst;
}

// 获取选套定额：把所有分类数据的必套定额确定好了先。选套定额就是清单下所有定额除了必套的
function getOptionalRationIDs(optionalRationData) {
    const optionalRationIDs = [];
    Object.values(optionalRationData).forEach(optionalRation => {
        if (optionalRation.length) optionalRationIDs.push(...optionalRation);
    })
    return [...new Set(optionalRationIDs)];
}

// 获取错套定额：清单下所有定额，除了分类对应的必套、选套定额
function getErrorRationIDs(requiredRationIDList, optionalRationIDs, allRationIDs) {
    const finRequireData = [];
    requiredRationIDList.forEach(requiredRationIDs => {
        finRequireData.push(...requiredRationIDs);
    })
    const errorRationIDs = [];
    allRationIDs.forEach(rationID => {
        if (!finRequireData.includes(rationID) && !optionalRationIDs.includes(rationID)) {
            errorRationIDs.push(rationID);
        }
    });
    return [...new Set(errorRationIDs)];
}

//把classcode和必套选套定额结合在一起
function combineData(codeData, requireRationData, optionalRationData, classGroups, IDData, keyGroup) {
    // 这里要记录下已经被绑定的选套定额，因为没有被用的定额需要绑定到各个classcode下
    const matchRationList = [];
    //这里需要把绑定在子节点的定额更新到必填的白色选项中（classcode的值）
    const requireCombineData = {};
    const optionCombineData = {};
    Object.keys(IDData).forEach(key => {
        if (!requireCombineData[IDData[key]]) requireCombineData[IDData[key]] = new Set();
        if (!optionCombineData[IDData[key]]) optionCombineData[IDData[key]] = new Set();
        if (requireRationData[key]) {
            requireRationData[key].forEach(subData => {
                subData.forEach(subItem => {
                    requireCombineData[IDData[key]].add(subItem);
                })
            })
        }
        if (optionalRationData[key]) {
            optionalRationData[key].forEach(subData => {
                optionCombineData[IDData[key]].add(subData);
            })
        }

    })
    const finData = codeData.map(classCodeData => {
        const errorRationIDs = [];
        const optionalRationIDs = [];
        const requiredRationIDs = [];
        let name = '';
        let key = ''; 
        const classCodeIDs = classCodeData.ID;
        if (/@/.test(classCodeIDs)) {
            classCodeIDs.split('@').forEach((classCodeID) => {
                if (name) {
                    name = name + '@' + classGroups[classCodeID];
                    key += keyGroup[classCodeID];
                } else {
                    name = classGroups[classCodeID];
                    key = keyGroup[classCodeID];
                };
                // 一组的必套定额，先去重
                const unitRation = [];
              
                // 这里是必填选项下绑定必套定额
                if (requireRationData[classCodeID] && requireRationData[classCodeID].length) {
                    requireRationData[classCodeID].forEach(subItem => {
                        unitRation.push(...new Set(subItem));
                    })
                    requiredRationIDs.push(unitRation);
                    // 这里也要把用过的必套定额先存起来
                    matchRationList.push(...unitRation);
                }
                //这里是必填选项下绑定在蓝色节点下的必套定额
                if (requireCombineData[classCodeID] && requireCombineData[classCodeID].size) {
                    requiredRationIDs.push([...requireCombineData[classCodeID]]);
                    // 这里也要把用过的必套定额先存起来
                    matchRationList.push(...requireCombineData[classCodeID]);
                }

                // 这里是必填选项下绑定选套定额
                if (optionalRationData[classCodeID] && optionalRationData[classCodeID].length) {
                    optionalRationIDs.push(...optionalRationData[classCodeID]);
                    matchRationList.push(...optionalRationData[classCodeID]);
                }
                //这里是必填选项下绑定在蓝色节点下的选套定额，下同
                if (optionCombineData[classCodeID] && optionCombineData[classCodeID].size) {
                    optionalRationIDs.push(...optionCombineData[classCodeID]);
                    matchRationList.push(...optionCombineData[classCodeID]);
                }

            })
            return { name, key, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs }
        } else {
            const unitRation = [];
            name = classGroups[classCodeIDs];
            key = keyGroup[classCodeIDs];
            if (requireRationData[classCodeIDs] && requireRationData[classCodeIDs].length){
                requireRationData[classCodeIDs].forEach(subItem => {
                    unitRation.push(...new Set(subItem));
                })
                requiredRationIDs.push(unitRation); 
                // 这里也要把用过的必套定额先存起来
                matchRationList.push(...unitRation);
            }
          
            if (requireCombineData[classCodeIDs] && requireCombineData[classCodeIDs].size) {
                requiredRationIDs.push([...requireCombineData[classCodeIDs]])
                 // 这里也要把用过的必套定额先存起来
                 matchRationList.push(...requireCombineData[classCodeIDs]);
            };

            if (optionalRationData[classCodeIDs] && optionalRationData[classCodeIDs].length) {
                optionalRationIDs.push(...optionalRationData[classCodeIDs]);
                matchRationList.push(...optionalRationData[classCodeIDs]);
            }
            if (optionCombineData[classCodeIDs] && optionCombineData[classCodeIDs].size) {
                optionalRationIDs.push(...optionCombineData[classCodeIDs]);
                matchRationList.push(...optionalRationData[classCodeIDs]);
            }
            return { name,key, requiredRationIDs, optionalRationIDs: [...new Set(optionalRationIDs)], errorRationIDs }
        }
    })
    const unMatchRation = [];
    Object.values(optionalRationData).forEach(data => {
        data.forEach((rationID) => {
            if (!matchRationList.includes(rationID)) {
                unMatchRation.push(rationID);
            }
        })
    })
    // 这里把没有使用过的必套定额也丢到了选套里面
    Object.values(requireRationData).forEach(data => {
        data.forEach((rationData) => {
            rationData.forEach(rationID=>{
                if (!matchRationList.includes(rationID)) {
                    unMatchRation.push(rationID);
                }
            })
        })
    })
    return { itemClassData: finData, unMatchRation };
}

// 生成清单分类
async function generateClassData(libID) {
    const lib = await billsGuideLibModel.findOne({ ID: libID }).lean();
    if (!lib) {
        throw new Error('无有效精灵库');
    }
    const guidanceItems = await billsGuideItemsModel.find({ libID }, '-_id').lean();
    // 清单ID - 指引数据映射
    const guidanceMap = {};
    guidanceItems.forEach(item => {
        (guidanceMap[item.billsID] || (guidanceMap[item.billsID] = [])).push(item);
    });
    const bills = await stdBillsModel.find({ billsLibId: lib.billsLibId }, '-_id ID ParentID NextSiblingID name code unit').lean();
    const billTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
    billTree.loadDatas(bills);
    // 叶子清单
    const leaves = billTree.items.filter(node => !node.children || !node.children.length);
    // 获取分类数据
    let classNum = 1;
    const billClassData = [];
    for (let billNode of leaves) {
        const guidanceItems = guidanceMap[billNode.data.ID];
        if (!guidanceItems || !guidanceItems.length) {
            continue;
        }
        // if (billNode.data.code.startsWith('03')) continue;//先屏蔽掉03开头的清单
        const guidanceTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
        guidanceTree.loadDatas(guidanceItems);
        //console.log('getItemClassData start',billNode.data.name,billNode.data.code,billNode.data.ID);
        const classCodeData = getItemClassData(guidanceTree.roots); // 必套定额在这个方法内就获取了，避免重复执行递归方法
        const { requireRationData, optionalRationData, classGroups, IDData, keyGroup } = getItemData(guidanceTree.roots);

        const { itemClassData, unMatchRation } = combineData(classCodeData, requireRationData, optionalRationData, classGroups, IDData, keyGroup);

        const allRationIDs = guidanceTree.items.filter(node => !!node.data.rationID).map(node => node.data.rationID);
        // 选套定额ID
        const optionalRationIDs = getOptionalRationIDs(optionalRationData);
        //if(itemClassData.length > 1000) console.log('getItemClassData end',billNode.data.name,billNode.data.code,billNode.data.ID,itemClassData.length)
        itemClassData.forEach(item => {
            // 错套定额
            item.optionalRationIDs.push(...unMatchRation);
            const errorRationIDs = getErrorRationIDs(item.requiredRationIDs, item.optionalRationIDs, allRationIDs);
            billClassData.push({
                itemCharacter: item.name,
                class: classNum++,
                classCode: `${billNode.data.code}@${item.name}`,
                compilationID: lib.compilationId,
                name: billNode.data.name,
                key: `${billNode.data.code}${billNode.data.name}${billNode.data.unit}${item.key}`,
                code: billNode.data.code,
                requiredRationIDs: item.requiredRationIDs || [],
                optionalRationIDs: item.optionalRationIDs || [],
                errorRationIDs,
            });
        });
        //新增无必填时的情况
        if (itemClassData.length === 0) {
            Object.keys(requireRationData).forEach((key) => {
                requireRationData[key].forEach(data => {
                    if (data.length) optionalRationIDs.push(...data);
                })
            })
           
            const errorRationIDs = getErrorRationIDs([], optionalRationIDs, allRationIDs);
           
            billClassData.push({
                itemCharacter: '',
                class: classNum++,
                classCode: `${billNode.data.code}`,
                compilationID: lib.compilationId,
                name: billNode.data.name,
                key:`${billNode.data.code}${billNode.data.name}${billNode.data.unit}`,
                code: billNode.data.code,
                requiredRationIDs: [],
                optionalRationIDs: optionalRationIDs || [],
                errorRationIDs,
            });
        }
    }
    console.log(`billClassData.length`);
    console.log(billClassData.length);
    // 清空旧的分类数据
    await billClassModel.deleteMany({ compilationID: lib.compilationId });
    // 之前遇到一次性插入40w条数据的情况，会卡死，循环插入速度快很多
    while (billClassData.length > 10000) {
        const list = billClassData.splice(0, 10000);
        await billClassModel.insertMany(list);
    }
    await billClassModel.insertMany(billClassData);

}

// 获取分类excel数据
async function getClassExcelData(libID) {
    const lib = await billsGuideLibModel.findOne({ ID: libID }, '-_id compilationId').lean();
    if (!lib) {
        throw new Error('无有效精灵库');
    }
    console.log('start');
    const date = Date.now();
    const classData = await billClassModel.find({ compilationID: lib.compilationId }).lean();
    console.log('end');
    console.log(Date.now() - date);
    const excelData = [['类别', '编码', '清单名称', '必填特征排列组合', '类别别名', '必套定额', '选套定额', '错套定额','基础样本']];
    classData.forEach(item => {
        const excelItem = [
            item.class,
            item.code,
            item.name,
            item.itemCharacter,
            item.classCode,
            (item.requiredRationIDs || []).join('@'),
            (item.optionalRationIDs || []).join('@'),
            (item.errorRationIDs || []).join('@'),
            item.key,
        ];
        excelData.push(excelItem);
    });
    return excelData;
}

async function testItems(libID) {
    let items = await billsGuideItemsModel.find({ libID: libID });
    //删除垃圾数据
    let delBulk = [];
    let itemsMapping = {};
    for (let item of items) {
        itemsMapping[item.ID] = true;
    }
    for (let item of items) {
        if (item.ParentID != -1 && !itemsMapping[item.ParentID]) {
            delBulk.push({
                deleteOne: {
                    filter: { ID: item.ID }
                }
            });
        }
    }
    if (delBulk.length > 0) {
        console.log(`delBulk.length`);
        console.log(delBulk.length);
        await billsGuideItemsModel.bulkWrite(delBulk);
    }
    /*   //查找同层节点含有相同NextSiblingID的节点
       let rst = [];
       let billsGroup = {};
       for(let item of items){
           if(!billsGroup[item.billsID]){
               billsGroup[item.billsID] = [item];
           }
           else {
               billsGroup[item.billsID].push(item);
           }
       }
       for(let bGroup in billsGroup){
           let group = billsGroup[bGroup];
           let parentGroup = {};
           for(let gItem of group){
               if(!parentGroup[gItem.ParentID]){
                   parentGroup[gItem.ParentID] = [gItem]
               }
               else {
                   parentGroup[gItem.ParentID].push(gItem);
               }
           }
           for(let pGroup in parentGroup){
               let pGroupData = parentGroup[pGroup];
               let nextGroup = {};
               for(let nItem of pGroupData){
                   let sameNext = _.filter(pGroupData, {NextSiblingID: nItem.NextSiblingID});
                   if(sameNext.length > 1){
                       console.log(`sameNext`);
                       console.log(sameNext);
                       if(!nextGroup[nItem.ParentID + nItem.NextSiblingID]){
                           rst.push({NextSiblingID: nItem.NextSiblingID, ParentID: nItem.ParentID});
                           nextGroup[nItem.ParentID + nItem.NextSiblingID] = 1;
                       }
                   }
               }
           }
   
       }*/
    return delBulk.length;
}

// 获取清单ID - 指引数据映射
async function getGuidanceMap(libID) {
    const guidanceItems = await billsGuideItemsModel.find({ libID }, '-_id').lean();
    // 清单ID - 指引数据映射
    const guidanceMap = {};
    guidanceItems.forEach(item => {
        (guidanceMap[item.billsID] || (guidanceMap[item.billsID] = [])).push(item);
    });
    return guidanceMap;
}

// 获取清单树
async function getBillTree(libID) {
    const lib = await billsGuideLibModel.findOne({ ID: libID }).lean();
    if (!lib) {
        throw new Error('无有效精灵库');
    }
    const bills = await stdBillsModel.find({ billsLibId: lib.billsLibId }, '-_id ID ParentID NextSiblingID name code unit').lean();
    const billTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
    billTree.loadDatas(bills);
    return billTree;
}

// 获取定额ID - 人材机数组 映射
async function getRationIDGljMap(rationIDs) {
    let allGljList = [];
    const rations = await stdRationModel.find({ ID: { $in: rationIDs } }, '-_id ID rationGljList');
    const gljIDs = [];
    rations.forEach(ration => {
        if (ration.rationGljList && ration.rationGljList.length) {
            gljIDs.push(...ration.rationGljList.map(rGlj => rGlj.gljId));
        }
    });
    if (gljIDs.length) {
        allGljList = await gljModel.find({ ID: { $in: [...new Set(gljIDs)] } }, '-_id ID code name specs gljType').lean();
    }
    const gljMap = {};
    allGljList.forEach(glj => gljMap[glj.ID] = glj);
    const rationIDGljMap = {};
    rations.forEach(ration => {
        if (ration.rationGljList && ration.rationGljList.length) {
            rationIDGljMap[ration.ID] = ration.rationGljList.map(rGlj => gljMap[rGlj.gljId]);
        } else {
            rationIDGljMap[ration.ID] = [];
        }
    });
    return rationIDGljMap;
}

// 需要自动配置材料的工序行
function needAutoMaterialNode(node) {
    const needAutoNames = [
        '水泥强度等级',
        '混凝土强度等级',
        '砂浆强度等级',
        '砌筑砂浆强度等级',
        '砂浆配合比',
        '砂强度等级',
        '构件混凝土强度等级',
    ];
    const name = (node.data.name || '').trim();
    return isProcessNode(node) && needAutoNames.includes(name);
}

// 获取需要自动配置的数据
function getNeedAutoSetData(leafBills, guidanceMap) {
    const allRationIDs = [];
    const toAutoSetData = []; // 准备要自动配置的数据
    const markMaterialIDs = []; // 需要打勾材料的数据ID
    for (let billNode of leafBills) {
        const guidanceItems = guidanceMap[billNode.data.ID];
        if (!guidanceItems || !guidanceItems.length) {
            continue;
        }
        const guidanceTree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
        guidanceTree.loadDatas(guidanceItems);
        const needAutoSetItems = guidanceTree.items.filter(node => needAutoMaterialNode(node));
        // 存在需要自动配置材料
        if (needAutoSetItems.length) {
            const rationIDs = guidanceTree.items
                .filter(node => node.data.rationID)
                .map(node => node.data.rationID);
            allRationIDs.push(...rationIDs);
            markMaterialIDs.push(...needAutoSetItems.map(node => node.data.ID));
            toAutoSetData.push({
                rationIDs, // 选择的材料只能从当前定额人材机下选
                billID: billNode.data.ID,
            });
        }
    }

    return { allRationIDs, markMaterialIDs, toAutoSetData };
}

const autoSetGljCodes = [
    '0401',
    '8001',
    '8003',
    '8005',
    '8009',
    '8011',
    '8013',
    '8015',
    '8017',
    '8019',
    '8021',
]

// 自动配置材料
async function autoSetMaterial(libID) {
    const billTree = await getBillTree(libID);
    const guidanceMap = await getGuidanceMap(libID);
    const leafBills = billTree.items.filter(node => !node.children || !node.children.length);
    const { allRationIDs, markMaterialIDs, toAutoSetData } = getNeedAutoSetData(leafBills, guidanceMap);
    if (!toAutoSetData.length) {
        return;
    }
    const rationIDGljMap = await getRationIDGljMap([...new Set(allRationIDs)]);
    const billMaterials = []; // 自动配置材料 （如果原本有配置，则替换原本的：删除旧的doc，插入新的doc）
    const billIDs = [];
    toAutoSetData.forEach(({ billID, rationIDs }) => {
        billIDs.push(billID);
        // 清单工料机
        const billGljList = rationIDs.reduce((list, cur) => {
            if (rationIDGljMap[cur]) {
                list.push(...rationIDGljMap[cur])
            }
            return list;
        }, []);
        // 从清单工料机列表中筛选，当前清单下定额工料机中存在的，配置到“配置材料”中，需要命中材料编码前四位
        const materialIDs = [];
        billGljList.forEach(glj => {
            const fourCode = (glj.code || '').substr(0, 4);
            if (autoSetGljCodes.includes(fourCode) && !materialIDs.includes(glj.ID)) {
                materialIDs.push(glj.ID);
            }
        });
        const materials = materialIDs.map(gljID => ({ gljID }));
        billMaterials.push({
            libID,
            billID,
            ID: uuidV1(),
            materials
        });
    });
    // 打勾材料
    await billsGuideItemsModel.updateMany({ ID: { $in: markMaterialIDs } }, { $set: { isMaterial: true } });
    // 删除要被替换的清单材料
    await billMaterialModel.deleteMany({ libID, billID: { $in: billIDs } });
    // 插入新的材料
    await billMaterialModel.insertMany(billMaterials);
}