package com.yhy.module.core.service.workbench.impl;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yhy.module.core.enums.ErrorCodeConstants.AUDIT_APPROVE_DIVISION_NOT_EXISTS;
import static com.yhy.module.core.enums.ErrorCodeConstants.AUDIT_MODE_ALREADY_EXISTS;
import static com.yhy.module.core.enums.ErrorCodeConstants.AUDIT_MODE_NOT_EXISTS;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.yhy.module.core.controller.admin.workbench.vo.WbBoqDivisionRespVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditApproveDivisionRespVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditApproveDivisionUpdateReqVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditDiffDivisionRespVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditDivisionTreeRespVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditModeCreateReqVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditModeRespVO;
import com.yhy.module.core.controller.admin.workbench.vo.audit.AuditSubmitDivisionRespVO;
import com.yhy.module.core.dal.dataobject.workbench.AuditApproveDivisionDO;
import com.yhy.module.core.dal.dataobject.workbench.AuditApproveResourceDO;
import com.yhy.module.core.dal.dataobject.workbench.AuditModeDO;
import com.yhy.module.core.dal.dataobject.workbench.WbBoqDivisionDO;
import com.yhy.module.core.dal.dataobject.workbench.WbBoqResourceDO;
import com.yhy.module.core.dal.dataobject.workbench.WbCompileTreeDO;
import com.yhy.module.core.dal.mysql.workbench.AuditApproveDivisionMapper;
import com.yhy.module.core.dal.mysql.workbench.AuditApproveResourceMapper;
import com.yhy.module.core.dal.mysql.workbench.AuditModeMapper;
import com.yhy.module.core.dal.mysql.workbench.WbBoqDivisionMapper;
import com.yhy.module.core.dal.mysql.workbench.WbBoqResourceMapper;
import com.yhy.module.core.dal.mysql.workbench.WbCompileTreeMapper;
import com.yhy.module.core.service.workbench.AuditModeService;
import com.yhy.module.core.service.workbench.WbBoqDivisionService;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

/**
 * 审核模式 Service 实现类
 *
 * @author yhy
 */
@Service
@Validated
@Slf4j
public class AuditModeServiceImpl implements AuditModeService {

    @Resource
    private AuditModeMapper auditModeMapper;


    @Resource
    private AuditApproveDivisionMapper auditApproveDivisionMapper;

    @Resource
    private AuditApproveResourceMapper auditApproveResourceMapper;

    @Resource
    private WbCompileTreeMapper wbCompileTreeMapper;

    @Resource
    private WbBoqResourceMapper wbBoqResourceMapper;

    @Resource
    private WbBoqDivisionMapper wbBoqDivisionMapper;

    @Resource
    @Lazy
    private WbBoqDivisionService wbBoqDivisionService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createAuditMode(AuditModeCreateReqVO createReqVO) {
        // 0. 校验：每个项目只允许创建一次审核模式
        List<AuditModeDO> existingModes = auditModeMapper.selectListByProjectId(createReqVO.getProjectId());
        if (CollUtil.isNotEmpty(existingModes)) {
            throw exception(AUDIT_MODE_ALREADY_EXISTS);
        }

        // 1. 创建审核模式主记录
        AuditModeDO auditMode = new AuditModeDO();
        auditMode.setProjectId(createReqVO.getProjectId());
        auditMode.setName(createReqVO.getName());
        auditMode.setStatus(AuditModeDO.STATUS_DRAFT);
        auditMode.setSubmitTime(LocalDateTime.now());
        auditModeMapper.insert(auditMode);

        // 2. 获取项目下所有单位工程节点
        List<WbCompileTreeDO> unitNodes = wbCompileTreeMapper.selectUnitNodesByProjectId(createReqVO.getProjectId());

        // 3. 为每个单位工程复制分部分项 → 审定快照表
        Map<Long, Long> divisionIdMapping = new HashMap<>();
        for (WbCompileTreeDO unitNode : unitNodes) {
            copyApproveDivisionsForUnit(auditMode.getId(), unitNode.getId(), divisionIdMapping);
        }

        // 4. 复制工料机 → 审定工料机快照表
        copyApproveResources(divisionIdMapping);

        return auditMode.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteAuditMode(Long id) {
        // 1. 校验存在
        AuditModeDO auditMode = auditModeMapper.selectById(id);
        if (auditMode == null) {
            throw exception(AUDIT_MODE_NOT_EXISTS);
        }

        // 2. 获取所有审定分部分项
        List<AuditApproveDivisionDO> divisions = auditApproveDivisionMapper.selectListByAuditModeId(id);
        List<Long> divisionIds = divisions.stream()
                .map(AuditApproveDivisionDO::getId)
                .collect(Collectors.toList());

        // 3. 删除审定工料机
        if (CollUtil.isNotEmpty(divisionIds)) {
            auditApproveResourceMapper.deleteByAuditApproveDivisionIds(divisionIds);
        }

        // 4. 删除审定分部分项
        auditApproveDivisionMapper.deleteByAuditModeId(id);

        // 5. 删除审核模式主记录
        auditModeMapper.deleteById(id);
    }

    @Override
    public AuditModeRespVO getAuditMode(Long id) {
        AuditModeDO auditMode = auditModeMapper.selectById(id);
        if (auditMode == null) {
            return null;
        }
        return BeanUtil.copyProperties(auditMode, AuditModeRespVO.class);
    }

    @Override
    public List<AuditModeRespVO> getAuditModeList(Long projectId) {
        List<AuditModeDO> list = auditModeMapper.selectListByProjectId(projectId);
        return BeanUtil.copyToList(list, AuditModeRespVO.class);
    }

    /**
     * 获取送审数据（联表查询编制模式实时数据）
     */
    private List<AuditSubmitDivisionRespVO> getSubmitTree(Long auditModeId, Long compileTreeId) {
        // 直接联表查询编制模式实时数据
        List<WbBoqDivisionRespVO> compileDivisions = wbBoqDivisionService.getTreeWithPrice(compileTreeId);

        // 转换为送审VO
        return convertToSubmitTree(compileDivisions);
    }

    /**
     * 获取审定数据（快照）
     */
    private List<AuditApproveDivisionRespVO> getApproveTree(Long auditModeId, Long compileTreeId) {
        // 直接查询审定快照表
        List<AuditApproveDivisionDO> list = auditApproveDivisionMapper
                .selectListByAuditModeIdAndCompileTreeId(auditModeId, compileTreeId);
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }

        // 转换为VO
        List<AuditApproveDivisionRespVO> voList = BeanUtil.copyToList(list, AuditApproveDivisionRespVO.class);

        // 构建树形结构
        return buildApproveTree(voList);
    }

    /**
     * 获取差异数据（送审 - 审定）
     */
    private List<AuditDiffDivisionRespVO> getDiffTree(Long auditModeId, Long compileTreeId) {
        // 1. 获取送审数据（编制模式实时数据）
        List<AuditSubmitDivisionRespVO> submitList = getSubmitTree(auditModeId, compileTreeId);

        // 2. 获取审定数据（快照）
        List<AuditApproveDivisionRespVO> approveList = getApproveTree(auditModeId, compileTreeId);

        // 3. 扁平化审定数据，按 sourceDivisionId 索引
        List<AuditApproveDivisionRespVO> flatApproveList = flattenApproveTree(approveList);
        Map<Long, AuditApproveDivisionRespVO> approveMap = flatApproveList.stream()
                .collect(Collectors.toMap(AuditApproveDivisionRespVO::getSourceDivisionId, a -> a, (a, b) -> a));

        // 4. 计算差异
        Set<Long> matchedIds = new HashSet<>();
        List<AuditDiffDivisionRespVO> diffList = calculateDiff(submitList, approveMap, matchedIds);

        // 5. 处理删除的节点（审定有，编制模式删除了）
        for (AuditApproveDivisionRespVO approve : flatApproveList) {
            if (!matchedIds.contains(approve.getSourceDivisionId())) {
                AuditDiffDivisionRespVO diff = new AuditDiffDivisionRespVO();
                diff.setSourceDivisionId(approve.getSourceDivisionId());
                diff.setParentId(approve.getParentId());
                diff.setNodeType(approve.getNodeType());
                diff.setCode(approve.getCode());
                diff.setName(approve.getName());
                diff.setUnit(approve.getUnit());
                diff.setDiffQty(negate(approve.getApproveQty()));
                diff.setDiffUnitPrice(negate(approve.getApproveUnitPrice()));
                diff.setDiffRate(negate(approve.getApproveRate()));
                diff.setDiffTotalPrice(negate(approve.getApproveTotalPrice()));
                diff.setChangeType(AuditDiffDivisionRespVO.CHANGE_TYPE_DELETE);
                diffList.add(diff);
            }
        }

        return diffList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateApproveData(AuditApproveDivisionUpdateReqVO updateReqVO) {
        // 1. 校验存在
        AuditApproveDivisionDO approve = auditApproveDivisionMapper.selectById(updateReqVO.getId());
        if (approve == null) {
            throw exception(AUDIT_APPROVE_DIVISION_NOT_EXISTS);
        }

        // 2. 获取原编制模式数据，用于比较
        WbBoqDivisionDO sourceDivision = null;
        if (approve.getSourceDivisionId() != null) {
            sourceDivision = wbBoqDivisionMapper.selectById(approve.getSourceDivisionId());
        }

        // 3. 更新基础信息（编码、名称、单位、项目特征）
        // 只有与原值不同时才存储，相同则设为null（引用原值）
        if (updateReqVO.getCode() != null) {
            String sourceCode = sourceDivision != null ? sourceDivision.getCode() : null;
            approve.setCode(StrUtil.equals(updateReqVO.getCode(), sourceCode) ? null : updateReqVO.getCode());
        }
        if (updateReqVO.getName() != null) {
            String sourceName = sourceDivision != null ? sourceDivision.getName() : null;
            approve.setName(StrUtil.equals(updateReqVO.getName(), sourceName) ? null : updateReqVO.getName());
        }
        if (updateReqVO.getUnit() != null) {
            String sourceUnit = sourceDivision != null ? sourceDivision.getUnit() : null;
            approve.setUnit(StrUtil.equals(updateReqVO.getUnit(), sourceUnit) ? null : updateReqVO.getUnit());
        }
        if (updateReqVO.getFeature() != null) {
            String sourceFeature = sourceDivision != null ? sourceDivision.getFeature() : null;
            approve.setFeature(StrUtil.equals(updateReqVO.getFeature(), sourceFeature) ? null : updateReqVO.getFeature());
        }

        // 4. 更新审定值
        if (updateReqVO.getApproveQty() != null) {
            approve.setApproveQty(updateReqVO.getApproveQty());
        }
        if (updateReqVO.getApproveUnitPrice() != null) {
            approve.setApproveUnitPrice(updateReqVO.getApproveUnitPrice());
        }
        if (updateReqVO.getApproveRate() != null) {
            approve.setApproveRate(updateReqVO.getApproveRate());
        }
        if (updateReqVO.getApproveTotalPrice() != null) {
            approve.setApproveTotalPrice(updateReqVO.getApproveTotalPrice());
        }

        auditApproveDivisionMapper.updateById(approve);
    }

    // ==================== 私有方法 ====================

    /**
     * 为单位工程复制分部分项到审定快照表
     */
    private void copyApproveDivisionsForUnit(Long auditModeId, Long compileTreeId, Map<Long, Long> divisionIdMapping) {
        // 获取编制模式的分部分项数据（含计算后的单价/合价）
        List<WbBoqDivisionRespVO> divisions = wbBoqDivisionService.getTreeWithPrice(compileTreeId);

        // 扁平化树结构
        List<WbBoqDivisionRespVO> flatDivisions = flattenDivisionTree(divisions);

        // 第一遍：创建所有节点，记录ID映射
        for (WbBoqDivisionRespVO div : flatDivisions) {
            AuditApproveDivisionDO auditDiv = new AuditApproveDivisionDO();
            auditDiv.setAuditModeId(auditModeId);
            auditDiv.setCompileTreeId(compileTreeId);
            auditDiv.setSourceDivisionId(div.getId());
            auditDiv.setNodeType(div.getNodeType());
            auditDiv.setSourceType(div.getSourceType());
            // code/name/unit/feature 默认为null，只有编辑且与原值不同时才存储
            // auditDiv.setCode(null);
            // auditDiv.setName(null);
            // auditDiv.setUnit(null);
            // auditDiv.setFeature(null);
            auditDiv.setSortOrder(div.getSortOrder());
            auditDiv.setPath(div.getPath());

            // 审定值 = 当前编制值
            auditDiv.setApproveQty(div.getQty());
            auditDiv.setApproveUnitPrice(div.getUnitPrice());
            auditDiv.setApproveRate(div.getRate());
            auditDiv.setApproveTotalPrice(div.getAmount());

            auditDiv.setAttributes(div.getAttributes());

            auditApproveDivisionMapper.insert(auditDiv);
            divisionIdMapping.put(div.getId(), auditDiv.getId());
        }

        // 第二遍：更新父节点ID
        for (WbBoqDivisionRespVO div : flatDivisions) {
            if (div.getParentId() != null) {
                Long newId = divisionIdMapping.get(div.getId());
                Long newParentId = divisionIdMapping.get(div.getParentId());
                if (newId != null && newParentId != null) {
                    AuditApproveDivisionDO auditDiv = auditApproveDivisionMapper.selectById(newId);
                    auditDiv.setParentId(newParentId);
                    auditApproveDivisionMapper.updateById(auditDiv);
                }
            }
        }
    }

    /**
     * 复制工料机到审定工料机快照表
     */
    private void copyApproveResources(Map<Long, Long> divisionIdMapping) {
        Map<Long, Long> resourceIdMapping = new HashMap<>();

        for (Map.Entry<Long, Long> entry : divisionIdMapping.entrySet()) {
            Long sourceDivisionId = entry.getKey();
            Long auditDivisionId = entry.getValue();

            // 获取工料机列表
            List<WbBoqResourceDO> resources = wbBoqResourceMapper.selectListByDivisionId(sourceDivisionId);
            if (CollUtil.isEmpty(resources)) {
                continue;
            }

            // 第一遍：创建所有工料机，记录ID映射
            for (WbBoqResourceDO res : resources) {
                AuditApproveResourceDO auditRes = new AuditApproveResourceDO();
                auditRes.setAuditApproveDivisionId(auditDivisionId);
                auditRes.setSourceResourceId(res.getId());
                auditRes.setResourceType(res.getResourceType());
                auditRes.setCode(res.getCode());
                auditRes.setName(res.getName());
                auditRes.setSpec(res.getSpec());
                auditRes.setUnit(res.getUnit());
                auditRes.setCategoryId(res.getCategoryId());
                auditRes.setSortOrder(res.getSortOrder());

                // 审定值 = 当前编制值
                auditRes.setApproveConsumeQty(res.getConsumeQty());
                auditRes.setApprovePrice(res.getTaxExclCompilePrice());

                auditRes.setAttributes(res.getAttributes());

                auditApproveResourceMapper.insert(auditRes);
                resourceIdMapping.put(res.getId(), auditRes.getId());
            }

            // 第二遍：更新父工料机ID
            for (WbBoqResourceDO res : resources) {
                if (res.getParentId() != null) {
                    Long newId = resourceIdMapping.get(res.getId());
                    Long newParentId = resourceIdMapping.get(res.getParentId());
                    if (newId != null && newParentId != null) {
                        AuditApproveResourceDO auditRes = auditApproveResourceMapper.selectById(newId);
                        auditRes.setParentId(newParentId);
                        auditApproveResourceMapper.updateById(auditRes);
                    }
                }
            }
        }
    }

    /**
     * 扁平化分部分项树
     */
    private List<WbBoqDivisionRespVO> flattenDivisionTree(List<WbBoqDivisionRespVO> tree) {
        List<WbBoqDivisionRespVO> result = new ArrayList<>();
        for (WbBoqDivisionRespVO node : tree) {
            result.add(node);
            if (CollUtil.isNotEmpty(node.getChildren())) {
                result.addAll(flattenDivisionTree(node.getChildren()));
            }
        }
        return result;
    }

    /**
     * 扁平化审定树
     */
    private List<AuditApproveDivisionRespVO> flattenApproveTree(List<AuditApproveDivisionRespVO> tree) {
        List<AuditApproveDivisionRespVO> result = new ArrayList<>();
        for (AuditApproveDivisionRespVO node : tree) {
            result.add(node);
            if (CollUtil.isNotEmpty(node.getChildren())) {
                result.addAll(flattenApproveTree(node.getChildren()));
            }
        }
        return result;
    }

    /**
     * 构建审定树形结构
     */
    private List<AuditApproveDivisionRespVO> buildApproveTree(List<AuditApproveDivisionRespVO> list) {
        Map<Long, AuditApproveDivisionRespVO> map = list.stream()
                .collect(Collectors.toMap(AuditApproveDivisionRespVO::getId, v -> v));

        List<AuditApproveDivisionRespVO> roots = new ArrayList<>();
        for (AuditApproveDivisionRespVO vo : list) {
            if (vo.getParentId() == null) {
                roots.add(vo);
            } else {
                AuditApproveDivisionRespVO parent = map.get(vo.getParentId());
                if (parent != null) {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(vo);
                }
            }
        }
        return roots;
    }

    /**
     * 转换为送审树
     */
    private List<AuditSubmitDivisionRespVO> convertToSubmitTree(List<WbBoqDivisionRespVO> compileDivisions) {
        List<AuditSubmitDivisionRespVO> result = new ArrayList<>();
        for (WbBoqDivisionRespVO compile : compileDivisions) {
            AuditSubmitDivisionRespVO submit = new AuditSubmitDivisionRespVO();
            submit.setSourceDivisionId(compile.getId());
            submit.setParentId(compile.getParentId());
            submit.setNodeType(compile.getNodeType());
            submit.setSourceType(compile.getSourceType());
            submit.setCode(compile.getCode());
            submit.setName(compile.getName());
            submit.setUnit(compile.getUnit());
            submit.setFeature(compile.getFeature());
            submit.setSortOrder(compile.getSortOrder());
            submit.setPath(compile.getPath());
            submit.setSubmitQty(compile.getQty());
            submit.setSubmitUnitPrice(compile.getUnitPrice());
            submit.setSubmitRate(compile.getRate());
            submit.setSubmitTotalPrice(compile.getAmount());
            submit.setAttributes(compile.getAttributes());

            if (CollUtil.isNotEmpty(compile.getChildren())) {
                submit.setChildren(convertToSubmitTree(compile.getChildren()));
            }

            result.add(submit);
        }
        return result;
    }

    /**
     * 计算差异
     */
    private List<AuditDiffDivisionRespVO> calculateDiff(
            List<AuditSubmitDivisionRespVO> submitList,
            Map<Long, AuditApproveDivisionRespVO> approveMap,
            Set<Long> matchedIds) {

        List<AuditDiffDivisionRespVO> result = new ArrayList<>();

        for (AuditSubmitDivisionRespVO submit : submitList) {
            AuditDiffDivisionRespVO diff = new AuditDiffDivisionRespVO();
            diff.setSourceDivisionId(submit.getSourceDivisionId());
            diff.setParentId(submit.getParentId());
            diff.setNodeType(submit.getNodeType());
            diff.setSourceType(submit.getSourceType());
            diff.setCode(submit.getCode());
            diff.setName(submit.getName());
            diff.setUnit(submit.getUnit());
            diff.setSortOrder(submit.getSortOrder());
            diff.setPath(submit.getPath());
            diff.setAttributes(submit.getAttributes());

            AuditApproveDivisionRespVO approve = approveMap.get(submit.getSourceDivisionId());
            if (approve != null) {
                matchedIds.add(submit.getSourceDivisionId());
                // 差异 = 送审 - 审定
                diff.setDiffQty(subtract(submit.getSubmitQty(), approve.getApproveQty()));
                diff.setDiffUnitPrice(subtract(submit.getSubmitUnitPrice(), approve.getApproveUnitPrice()));
                diff.setDiffRate(subtract(submit.getSubmitRate(), approve.getApproveRate()));
                diff.setDiffTotalPrice(subtract(submit.getSubmitTotalPrice(), approve.getApproveTotalPrice()));
                diff.setChangeType(isAllZero(diff) ? AuditDiffDivisionRespVO.CHANGE_TYPE_NONE
                        : AuditDiffDivisionRespVO.CHANGE_TYPE_MODIFY);
            } else {
                // 新增节点（编制模式新增，审定快照没有）
                diff.setDiffQty(submit.getSubmitQty());
                diff.setDiffUnitPrice(submit.getSubmitUnitPrice());
                diff.setDiffRate(submit.getSubmitRate());
                diff.setDiffTotalPrice(submit.getSubmitTotalPrice());
                diff.setChangeType(AuditDiffDivisionRespVO.CHANGE_TYPE_ADD);
            }

            // 递归处理子节点
            if (CollUtil.isNotEmpty(submit.getChildren())) {
                diff.setChildren(calculateDiff(submit.getChildren(), approveMap, matchedIds));
            }

            result.add(diff);
        }

        return result;
    }

    /**
     * 减法（处理null）
     */
    private BigDecimal subtract(BigDecimal a, BigDecimal b) {
        if (a == null && b == null) {
            return BigDecimal.ZERO;
        }
        if (a == null) {
            return b.negate();
        }
        if (b == null) {
            return a;
        }
        return a.subtract(b);
    }

    /**
     * 取反（处理null）
     */
    private BigDecimal negate(BigDecimal value) {
        return value == null ? null : value.negate();
    }

    /**
     * 判断差异是否全为0
     */
    private boolean isAllZero(AuditDiffDivisionRespVO diff) {
        return isZero(diff.getDiffQty())
                && isZero(diff.getDiffUnitPrice())
                && isZero(diff.getDiffRate())
                && isZero(diff.getDiffTotalPrice());
    }

    /**
     * 判断是否为0
     */
    private boolean isZero(BigDecimal value) {
        return value == null || value.compareTo(BigDecimal.ZERO) == 0;
    }

    @Override
    public List<AuditDivisionTreeRespVO> getDivisionTree(Long auditModeId, Long compileTreeId) {
        // 1. 获取送审数据（编制模式实时）
        List<AuditSubmitDivisionRespVO> submitList = getSubmitTree(auditModeId, compileTreeId);

        // 2. 获取审定数据（快照）
        List<AuditApproveDivisionRespVO> approveList = getApproveTree(auditModeId, compileTreeId);

        // 3. 扁平化数据，建立索引
        Map<Long, AuditSubmitDivisionRespVO> submitMap = flattenSubmitTree(submitList).stream()
                .collect(Collectors.toMap(AuditSubmitDivisionRespVO::getSourceDivisionId, v -> v, (a, b) -> a));
        Map<Long, AuditApproveDivisionRespVO> approveMap = flattenApproveTree(approveList).stream()
                .collect(Collectors.toMap(AuditApproveDivisionRespVO::getSourceDivisionId, v -> v, (a, b) -> a));

        // 4. 以审定数据为基准合并（因为审定有ID，需要用于编辑）
        Set<Long> processedSourceIds = new HashSet<>();
        List<AuditDivisionTreeRespVO> result = mergeDivisionTree(approveList, submitMap, processedSourceIds);

        // 5. 处理新增节点（送审有，审定无）
        addNewNodes(result, submitList, approveMap);

        return result;
    }

    /**
     * 扁平化送审树
     */
    private List<AuditSubmitDivisionRespVO> flattenSubmitTree(List<AuditSubmitDivisionRespVO> tree) {
        List<AuditSubmitDivisionRespVO> result = new ArrayList<>();
        if (tree == null) {
            return result;
        }
        for (AuditSubmitDivisionRespVO node : tree) {
            result.add(node);
            if (CollUtil.isNotEmpty(node.getChildren())) {
                result.addAll(flattenSubmitTree(node.getChildren()));
            }
        }
        return result;
    }

    /**
     * 合并分部分项树（以审定为基准）
     * 定额节点拆分为送审行+审核行，差异只保留清单级
     */
    private List<AuditDivisionTreeRespVO> mergeDivisionTree(
            List<AuditApproveDivisionRespVO> approveList,
            Map<Long, AuditSubmitDivisionRespVO> submitMap,
            Set<Long> processedSourceIds) {

        List<AuditDivisionTreeRespVO> result = new ArrayList<>();
        if (approveList == null) {
            return result;
        }

        for (AuditApproveDivisionRespVO approve : approveList) {
            boolean isQuota = AuditApproveDivisionDO.NODE_TYPE_QUOTA.equals(approve.getNodeType());
            AuditSubmitDivisionRespVO submit = submitMap.get(approve.getSourceDivisionId());

            if (isQuota) {
                // ========== 定额节点：拆分为送审行+审核行 ==========
                if (submit != null) {
                    processedSourceIds.add(approve.getSourceDivisionId());
                }
                // 送审行和审核行分别收集，后续统一添加到父节点children
                // 这里先添加到result，由调用方处理排序
                result.addAll(buildQuotaSplitRows(approve, submit));
            } else {
                // ========== 非定额节点：保持合并逻辑 ==========
                AuditDivisionTreeRespVO vo = buildMergedNonQuotaRow(approve, submit, processedSourceIds);

                // 递归处理子节点，并对定额子节点做分组排序
                if (CollUtil.isNotEmpty(approve.getChildren())) {
                    List<AuditDivisionTreeRespVO> childResult = mergeDivisionTree(approve.getChildren(), submitMap, processedSourceIds);
                    // 对清单节点的子节点做分组排序：先送审定额，后审核定额
                    if (AuditApproveDivisionDO.NODE_TYPE_BOQ.equals(approve.getNodeType())) {
                        childResult = sortQuotaChildren(childResult);
                    }
                    vo.setChildren(childResult);
                }

                result.add(vo);
            }
        }

        return result;
    }

    /**
     * 构建定额节点的送审行+审核行（一拆二）
     */
    private List<AuditDivisionTreeRespVO> buildQuotaSplitRows(
            AuditApproveDivisionRespVO approve,
            AuditSubmitDivisionRespVO submit) {

        List<AuditDivisionTreeRespVO> rows = new ArrayList<>();

        // 1. 送审行（不可编辑，只填送审列组）
        if (submit != null) {
            AuditDivisionTreeRespVO submitRow = new AuditDivisionTreeRespVO();
            // 不设id，送审行不可编辑
            submitRow.setSourceDivisionId(approve.getSourceDivisionId());
            submitRow.setParentId(approve.getParentId());
            submitRow.setNodeType(approve.getNodeType());
            submitRow.setSourceType(approve.getSourceType());
            submitRow.setSortOrder(approve.getSortOrder());
            submitRow.setPath(approve.getPath());
            submitRow.setAttributes(approve.getAttributes());
            submitRow.setDataSource(AuditDivisionTreeRespVO.DATA_SOURCE_SUBMIT);
            submitRow.setEditable(false);

            // 基础信息取编制模式实时值
            submitRow.setCode(submit.getCode());
            submitRow.setName(submit.getName());
            submitRow.setUnit(submit.getUnit());
            submitRow.setFeature(submit.getFeature());
            submitRow.setSubmitCode(submit.getCode());
            submitRow.setSubmitName(submit.getName());
            submitRow.setSubmitUnit(submit.getUnit());
            submitRow.setSubmitFeature(submit.getFeature());
            submitRow.setCodeEdited(false);
            submitRow.setNameEdited(false);
            submitRow.setUnitEdited(false);
            submitRow.setFeatureEdited(false);

            // 送审数值
            submitRow.setSubmitQty(submit.getSubmitQty());
            submitRow.setSubmitUnitPrice(submit.getSubmitUnitPrice());
            submitRow.setSubmitRate(submit.getSubmitRate());
            submitRow.setSubmitTotalPrice(submit.getSubmitTotalPrice());

            // 审核列组和差异列组留null（定额不计算差异）
            submitRow.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_NONE);

            rows.add(submitRow);
        }

        // 2. 审核行（可编辑，只填审核列组）
        AuditDivisionTreeRespVO approveRow = new AuditDivisionTreeRespVO();
        approveRow.setId(approve.getId()); // 保留审定表ID，用于编辑保存
        approveRow.setSourceDivisionId(approve.getSourceDivisionId());
        approveRow.setParentId(approve.getParentId());
        approveRow.setNodeType(approve.getNodeType());
        approveRow.setSourceType(approve.getSourceType());
        approveRow.setSortOrder(approve.getSortOrder());
        approveRow.setPath(approve.getPath());
        approveRow.setAttributes(approve.getAttributes());
        approveRow.setUpdateTime(approve.getUpdateTime());
        approveRow.setDataSource(AuditDivisionTreeRespVO.DATA_SOURCE_APPROVE);
        approveRow.setEditable(true);

        // 基础信息：编辑过则显示审定值，否则显示编制模式值
        boolean codeEdited = approve.getCode() != null;
        boolean nameEdited = approve.getName() != null;
        boolean unitEdited = approve.getUnit() != null;
        boolean featureEdited = approve.getFeature() != null;
        approveRow.setCodeEdited(codeEdited);
        approveRow.setNameEdited(nameEdited);
        approveRow.setUnitEdited(unitEdited);
        approveRow.setFeatureEdited(featureEdited);

        if (submit != null) {
            approveRow.setCode(codeEdited ? approve.getCode() : submit.getCode());
            approveRow.setName(nameEdited ? approve.getName() : submit.getName());
            approveRow.setUnit(unitEdited ? approve.getUnit() : submit.getUnit());
            approveRow.setFeature(featureEdited ? approve.getFeature() : submit.getFeature());
            approveRow.setSubmitCode(submit.getCode());
            approveRow.setSubmitName(submit.getName());
            approveRow.setSubmitUnit(submit.getUnit());
            approveRow.setSubmitFeature(submit.getFeature());
        } else {
            // 编制模式已删除该定额，显示审定表中存储的值
            approveRow.setCode(approve.getCode());
            approveRow.setName(approve.getName());
            approveRow.setUnit(approve.getUnit());
            approveRow.setFeature(approve.getFeature());
        }

        // 审核数值
        approveRow.setApproveQty(approve.getApproveQty());
        approveRow.setApproveUnitPrice(approve.getApproveUnitPrice());
        approveRow.setApproveRate(approve.getApproveRate());
        approveRow.setApproveTotalPrice(approve.getApproveTotalPrice());

        // 差异列组留null（定额不计算差异）
        if (submit == null) {
            approveRow.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_DELETE);
        } else {
            approveRow.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_NONE);
        }

        rows.add(approveRow);

        return rows;
    }

    /**
     * 构建非定额节点的合并行（保持原逻辑，差异只保留清单级）
     */
    private AuditDivisionTreeRespVO buildMergedNonQuotaRow(
            AuditApproveDivisionRespVO approve,
            AuditSubmitDivisionRespVO submit,
            Set<Long> processedSourceIds) {

        AuditDivisionTreeRespVO vo = new AuditDivisionTreeRespVO();

        // 基础字段
        vo.setId(approve.getId());
        vo.setSourceDivisionId(approve.getSourceDivisionId());
        vo.setParentId(approve.getParentId());
        vo.setNodeType(approve.getNodeType());
        vo.setSourceType(approve.getSourceType());
        vo.setSortOrder(approve.getSortOrder());
        vo.setPath(approve.getPath());
        vo.setAttributes(approve.getAttributes());
        vo.setUpdateTime(approve.getUpdateTime());

        // 审定数据
        vo.setApproveQty(approve.getApproveQty());
        vo.setApproveUnitPrice(approve.getApproveUnitPrice());
        vo.setApproveRate(approve.getApproveRate());
        vo.setApproveTotalPrice(approve.getApproveTotalPrice());

        if (submit != null) {
            processedSourceIds.add(approve.getSourceDivisionId());
            // 送审基础信息
            vo.setSubmitCode(submit.getCode());
            vo.setSubmitName(submit.getName());
            vo.setSubmitUnit(submit.getUnit());
            vo.setSubmitFeature(submit.getFeature());

            boolean codeEdited = approve.getCode() != null;
            boolean nameEdited = approve.getName() != null;
            boolean unitEdited = approve.getUnit() != null;
            boolean featureEdited = approve.getFeature() != null;
            vo.setCodeEdited(codeEdited);
            vo.setNameEdited(nameEdited);
            vo.setUnitEdited(unitEdited);
            vo.setFeatureEdited(featureEdited);

            vo.setCode(codeEdited ? approve.getCode() : submit.getCode());
            vo.setName(nameEdited ? approve.getName() : submit.getName());
            vo.setUnit(unitEdited ? approve.getUnit() : submit.getUnit());
            vo.setFeature(featureEdited ? approve.getFeature() : submit.getFeature());

            // 送审数值
            vo.setSubmitQty(submit.getSubmitQty());
            vo.setSubmitUnitPrice(submit.getSubmitUnitPrice());
            vo.setSubmitRate(submit.getSubmitRate());
            vo.setSubmitTotalPrice(submit.getSubmitTotalPrice());

            // 差异只保留清单(boq)、分部(division)、根(root)级别
            String nodeType = approve.getNodeType();
            if (AuditApproveDivisionDO.NODE_TYPE_BOQ.equals(nodeType)
                    || AuditApproveDivisionDO.NODE_TYPE_DIVISION.equals(nodeType)
                    || AuditApproveDivisionDO.NODE_TYPE_ROOT.equals(nodeType)) {
                vo.setDiffQty(subtract(submit.getSubmitQty(), approve.getApproveQty()));
                vo.setDiffUnitPrice(subtract(submit.getSubmitUnitPrice(), approve.getApproveUnitPrice()));
                vo.setDiffRate(subtract(submit.getSubmitRate(), approve.getApproveRate()));
                vo.setDiffTotalPrice(subtract(submit.getSubmitTotalPrice(), approve.getApproveTotalPrice()));
            }
            // 其他非定额节点（如unified_fee）不计算差异

            if (isAllZeroForMerged(vo)) {
                vo.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_NONE);
            } else {
                vo.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_MODIFY);
            }
        } else {
            // 送审无，审定有 → delete
            vo.setCode(approve.getCode());
            vo.setName(approve.getName());
            vo.setUnit(approve.getUnit());
            vo.setFeature(approve.getFeature());
            vo.setCodeEdited(false);
            vo.setNameEdited(false);
            vo.setUnitEdited(false);
            vo.setFeatureEdited(false);

            vo.setDiffQty(negate(approve.getApproveQty()));
            vo.setDiffUnitPrice(negate(approve.getApproveUnitPrice()));
            vo.setDiffRate(negate(approve.getApproveRate()));
            vo.setDiffTotalPrice(negate(approve.getApproveTotalPrice()));
            vo.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_DELETE);
        }

        return vo;
    }

    /**
     * 对清单子节点做分组排序：先送审定额，后审核定额，非定额节点保持原位
     */
    private List<AuditDivisionTreeRespVO> sortQuotaChildren(List<AuditDivisionTreeRespVO> children) {
        if (children == null || children.isEmpty()) {
            return children;
        }
        // 分三组：非定额节点、送审定额、审核定额
        List<AuditDivisionTreeRespVO> nonQuota = new ArrayList<>();
        List<AuditDivisionTreeRespVO> submitQuotas = new ArrayList<>();
        List<AuditDivisionTreeRespVO> approveQuotas = new ArrayList<>();

        for (AuditDivisionTreeRespVO child : children) {
            if (!AuditApproveDivisionDO.NODE_TYPE_QUOTA.equals(child.getNodeType())) {
                nonQuota.add(child);
            } else if (AuditDivisionTreeRespVO.DATA_SOURCE_SUBMIT.equals(child.getDataSource())) {
                submitQuotas.add(child);
            } else {
                approveQuotas.add(child);
            }
        }

        // 合并：非定额节点 + 送审定额 + 审核定额
        List<AuditDivisionTreeRespVO> sorted = new ArrayList<>();
        sorted.addAll(nonQuota);
        sorted.addAll(submitQuotas);
        sorted.addAll(approveQuotas);
        return sorted;
    }

    /**
     * 添加新增节点（送审有，审定无）
     * 定额节点只生成送审行，非定额节点保持原逻辑
     */
    private void addNewNodes(
            List<AuditDivisionTreeRespVO> result,
            List<AuditSubmitDivisionRespVO> submitList,
            Map<Long, AuditApproveDivisionRespVO> approveMap) {

        if (submitList == null) {
            return;
        }

        for (AuditSubmitDivisionRespVO submit : submitList) {
            if (!approveMap.containsKey(submit.getSourceDivisionId())) {
                boolean isQuota = AuditApproveDivisionDO.NODE_TYPE_QUOTA.equals(submit.getNodeType());

                // 新增节点
                AuditDivisionTreeRespVO vo = new AuditDivisionTreeRespVO();
                vo.setSourceDivisionId(submit.getSourceDivisionId());
                vo.setParentId(submit.getParentId());
                vo.setNodeType(submit.getNodeType());
                vo.setSourceType(submit.getSourceType());
                vo.setCode(submit.getCode());
                vo.setName(submit.getName());
                vo.setUnit(submit.getUnit());
                vo.setFeature(submit.getFeature());
                vo.setSortOrder(submit.getSortOrder());
                vo.setPath(submit.getPath());
                vo.setAttributes(submit.getAttributes());

                // 送审基础信息
                vo.setSubmitCode(submit.getCode());
                vo.setSubmitName(submit.getName());
                vo.setSubmitUnit(submit.getUnit());
                vo.setSubmitFeature(submit.getFeature());

                vo.setCodeEdited(false);
                vo.setNameEdited(false);
                vo.setUnitEdited(false);
                vo.setFeatureEdited(false);

                // 送审数值
                vo.setSubmitQty(submit.getSubmitQty());
                vo.setSubmitUnitPrice(submit.getSubmitUnitPrice());
                vo.setSubmitRate(submit.getSubmitRate());
                vo.setSubmitTotalPrice(submit.getSubmitTotalPrice());

                if (isQuota) {
                    // 新增定额：只生成送审行，标记为submit
                    vo.setDataSource(AuditDivisionTreeRespVO.DATA_SOURCE_SUBMIT);
                    vo.setEditable(false);
                    // 定额不计算差异
                } else {
                    // 非定额新增节点：差异 = 送审值
                    vo.setDiffQty(submit.getSubmitQty());
                    vo.setDiffUnitPrice(submit.getSubmitUnitPrice());
                    vo.setDiffRate(submit.getSubmitRate());
                    vo.setDiffTotalPrice(submit.getSubmitTotalPrice());
                }
                vo.setChangeType(AuditDivisionTreeRespVO.CHANGE_TYPE_ADD);

                result.add(vo);
            }

            // 递归处理子节点
            if (CollUtil.isNotEmpty(submit.getChildren())) {
                addNewNodes(result, submit.getChildren(), approveMap);
            }
        }
    }

    /**
     * 判断合并后的差异是否全为0
     */
    private boolean isAllZeroForMerged(AuditDivisionTreeRespVO vo) {
        return isZero(vo.getDiffQty())
                && isZero(vo.getDiffUnitPrice())
                && isZero(vo.getDiffRate())
                && isZero(vo.getDiffTotalPrice());
    }
}
