import json
import os
from copy import deepcopy
from typing import Dict, Any, Optional, Set

# 添加全局缓存
_bill_node_cache = {}


def find_project_division_node(node, project_name, project_guid=None, result=None):
    """递归查找指定项目名称的项目划分节点，可选匹配GUID"""
    if result is not None and result:
        return result

    if isinstance(node, dict):
        # 检查是否是项目划分节点
        is_project_division = node.get("type") == "项目划分"
        # 检查项目名称是否匹配
        name_matches = node.get("项目名称") == project_name
        # 检查GUID是否匹配（如果提供了GUID）
        guid_matches = True
        if project_guid:
            node_guid = node.get("GUID") or node.get("guid")
            guid_matches = node_guid == project_guid

        # 如果是项目划分节点，且名称和GUID都匹配（或没有提供GUID）
        if is_project_division and name_matches and guid_matches:
            return [node]

        # 递归查找子节点
        for key, value in node.items():
            if isinstance(value, (dict, list)):
                result = find_project_division_node(value, project_name, project_guid, result)
                if result:
                    return result

    elif isinstance(node, list):
        for item in node:
            if isinstance(item, (dict, list)):
                result = find_project_division_node(item, project_name, project_guid, result)
                if result:
                    return result

    return result or []


def find_cost_table(cost_setting, table_name):
    """在costSetting中查找指定名称的取费表"""
    if isinstance(cost_setting, dict):
        if cost_setting.get("name") == table_name:
            return cost_setting

        for key, value in cost_setting.items():
            if isinstance(value, (dict, list)):
                result = find_cost_table(value, table_name)
                if result:
                    return result

    elif isinstance(cost_setting, list):
        for item in cost_setting:
            if isinstance(item, (dict, list)):
                result = find_cost_table(item, table_name)
                if result:
                    return result

    return None


def map_quantity_node_types(node):
    """映射工程量节点的类型和费用类型"""
    if not isinstance(node, dict):
        return node

    # 复制节点以避免修改原始数据
    node_copy = deepcopy(node)

    # 先根据“配件判定规则”标准化类型：
    # 若 节点的 type/类型 == "配件" 且 配型 == "配件"，则检查 配件类型：
    #   - 配件类型 == "主材" -> 作为 主材 节点
    #   - 配件类型 == "配件" -> 作为 设备 节点
    raw_type = node_copy.get("类型") if node_copy.get("类型") is not None else node_copy.get("type")
    raw_peixing = node_copy.get("配型")
    # 放宽条件：当 类型/type 为“配件”，且（配型为“配件”或缺省），并且存在“配件类型”时，按配件类型映射
    if str(raw_type) == "配件" and (raw_peixing in (None, "", "配件")):
        pj_type = node_copy.get("配件类型")
        if pj_type == "主材":
            node_copy["类型"] = "主材"
            node_copy["type"] = "主材"
        elif pj_type == "配件":
            node_copy["类型"] = "设备"
            node_copy["type"] = "设备"

    # 映射类型字段
    if "类型" in node_copy:
        type_mapping = {"0": "定额", "1": "主材", "5": "设备"}
        if node_copy["类型"] in type_mapping:
            node_copy["类型"] = type_mapping[node_copy["类型"]]

    # 映射费用类型字段
    if "费用类型" in node_copy:
        if node_copy["费用类型"] == "0":
            node_copy["费用类型"] = "取费"
        # 如果已经是"取费"则保持不变

    # 不在此处递归 children，避免对子节点进行类型标准化，交由各层独立处理
    # if isinstance(node_copy.get("children"), list):
    #     node_copy["children"] = [map_quantity_node_types(child) for child in node_copy["children"]]

    return node_copy


def map_resource_node_types(node):
    """映射人材机节点的类型"""
    if not isinstance(node, dict):
        return node

    # 复制节点以避免修改原始数据
    node_copy = deepcopy(node)

    # 统一标准化类型字段：同时规范化 '类型' 与 'type'，输出为中文（人工/材料/机械）
    type_mapping = {"2": "人工", "3": "材料", "4": "机械"}
    # 取原始值（可能存在任一字段）
    raw_t = node_copy.get("类型") if node_copy.get("类型") is not None else node_copy.get("type")
    if raw_t is not None:
        # 若为代码，转换为中文；若已为中文则保持
        normalized = type_mapping.get(str(raw_t), raw_t)
        node_copy["类型"] = normalized
        node_copy["type"] = normalized

    # 递归处理子节点
    if "children" in node_copy and node_copy["children"]:
        node_copy["children"] = [map_resource_node_types(child) for child in node_copy["children"]]

    return node_copy


def extract_resource_nodes(node, parent_id=None):
    """
    提取人材机节点并保持父子结构

    Args:
        node: 节点
        parent_id: 父级节点ID

    Returns:
        list: 提取的人材机节点列表，每个节点包含parent_id字段
    """
    if not isinstance(node, dict):
        return []  # 返回空列表而不是None，便于后续处理

    # 类型工具
    def _is_resource_type(t):
        return t in ("人工", "材料", "机械", "2", "3", "4")

    str2code = {"人工": "2", "材料": "3", "机械": "4"}
    code2str = {v: k for k, v in str2code.items()}

    # 获取当前节点ID
    current_id = node.get("id") or node.get("GUID") or node.get("guid")
    resource_nodes = []

    # 检查是否是人材机节点（支持字符串和数字类型、双字段）
    node_type = node.get("类型") or node.get("type")
    is_resource_node = _is_resource_type(node_type)

    # 合并处理材机列表和children字段    # 处理逻辑：
    # - 若为资源节点且存在children：不返回父节点；其children继承父类型后继续递归提取
    # - 若为资源节点且无children：返回该节点
    # - 若非资源节点：常规递归处理children/材机列表

    # 优先处理“材机列表”
    child_nodes = []
    if "材机列表" in node and node["材机列表"]:
        child_nodes = node["材机列表"]
    elif "children" in node and node["children"]:
        child_nodes = node["children"]

    if is_resource_node:
        if child_nodes:
            # 父为资源且有子：上提子项，子项继承父类型（同时设置中英文字段），并递归
            parent_code = str2code.get(node_type, node_type)
            parent_str = code2str.get(node_type, node_type)
            for child in child_nodes:
                child_copy = deepcopy(child)
                if isinstance(child_copy, dict):
                    child_copy["parent_id"] = current_id
                    child_copy["type"] = parent_code
                    child_copy["类型"] = parent_str
                    sub_resources = extract_resource_nodes(child_copy, current_id)
                    if sub_resources:
                        resource_nodes.extend(sub_resources)
                else:
                    # 基本类型直接忽略
                    continue
        else:
            # 父为资源且无子：直接计入
            node_copy = deepcopy(node)
            node_copy["parent_id"] = parent_id
            # 统一双字段
            node_copy["type"] = str2code.get(node_type, node_type)
            node_copy["类型"] = code2str.get(node_type, node_type)
            resource_nodes.append(node_copy)
    else:
        # 非资源节点：递归其子节点
        for child in child_nodes:
            child_copy = deepcopy(child)
            if isinstance(child_copy, dict):
                child_copy["parent_id"] = current_id
                sub_resources = extract_resource_nodes(child_copy, current_id)
                if sub_resources:
                    resource_nodes.extend(sub_resources)

    return resource_nodes


def process_project_children(children, software_type=None):
    """处理项目子节点，分离工程量节点和人材机节点，支持嵌套的工程量节点"""
    if not children:
        return None, None

    quantity_nodes = []
    resource_nodes = []

    # 内部工具：在人材机存在拆分时展开其子节点，删除中间父级人材机节点
    def _flatten_resource_splits_in_place(node: dict):
        """
        递归+迭代地展开任意深度的人材机拆分：
        - 如果某个子节点是人材机(人工/材料/机械/2/3/4)且其自身有children：
          * 将其children上提为当前层的children；
          * 被上提的每个子节点的 类型/type 设为与父节点相同；
          * 移除该父级人材机节点本身；
        - 对非被移除的子节点递归处理；
        通过 while 循环保证多层嵌套被完全展开。
        """
        if not isinstance(node, dict):
            return
        if "children" not in node or not node["children"]:
            return

        def _is_resource_type(t):
            return t in ("人工", "材料", "机械", "2", "3", "4")

        # 中英文类型映射
        str2code = {"人工": "2", "材料": "3", "机械": "4"}
        code2str = {v: k for k, v in str2code.items()}

        changed = True
        while changed:
            changed = False
            new_children = []
            for ch in node["children"]:
                if isinstance(ch, dict):
                    t = ch.get("类型") or ch.get("type")
                    # 命中“人材机且存在children”，则将其子节点上提并继承类型
                    if _is_resource_type(t) and ch.get("children"):
                        parent_code = str2code.get(t, t)  # 若 t 已是代码则保持
                        parent_str = code2str.get(t, t)  # 若 t 已是中文则保持
                        for gc in ch.get("children", []) or []:
                            if isinstance(gc, dict):
                                gc["type"] = parent_code
                                gc["类型"] = parent_str
                                new_children.append(gc)
                            else:
                                new_children.append(gc)
                        # 丢弃父级人材机节点
                        changed = True
                        continue
                    else:
                        # 对未被移除的子节点继续递归展开
                        _flatten_resource_splits_in_place(ch)
                        new_children.append(ch)
                else:
                    new_children.append(ch)
            node["children"] = new_children

    for child in children:
        # 先复制两份：一份用于工程量树(quantity_node)，一份用于资源提取(resource_node_src)
        quantity_node = deepcopy(child)
        resource_node_src = deepcopy(child)
        # 额外保留一份原始工程量节点用于最终输出，保持原样不作修改
        original_quantity_node = deepcopy(child)

        # 在两份结构中都先执行“人材机拆分展开”，以便：
        # 1) 工程量树不保留中间的人材机父节点
        # 2) 资源提取时直接提取到展开后的子资源，且不包含中间父节点
        _flatten_resource_splits_in_place(quantity_node)
        _flatten_resource_splits_in_place(resource_node_src)

        # 应用工程量节点类型映射
        quantity_node = map_quantity_node_types(quantity_node)

        # 在“技改/主网”下，对定额节点做嵌套检测（children 或 材机列表 中包含 定额/主材/设备 或 配件类型为 主材/配件）
        if software_type in ("技改", "主网"):

            def _type_is_quota_material_equipment(t):
                # 支持代码与中文类型名
                return t in ("定额", "主材", "设备", "0", "1", "5")

            def _collect_nested_matches(node):
                matches = []
                # 检查 children
                for ch in node.get("children", []) or []:
                    if isinstance(ch, dict):
                        t = ch.get("类型") or ch.get("type")
                        pj_t = ch.get("配件类型")
                        if _type_is_quota_material_equipment(t) or pj_t in ("主材", "配件"):
                            matches.append(
                                {
                                    "来源": "children",
                                    "名称": ch.get("项目名称") or ch.get("清单名称") or ch.get("name"),
                                    "类型": t,
                                    "配件类型": pj_t,
                                    "id": ch.get("GUID") or ch.get("guid") or ch.get("id"),
                                }
                            )
                        # 递归继续检查更深层的嵌套
                        sub = _collect_nested_matches(ch)
                        if sub:
                            matches.extend(sub)

                # 检查 材机列表（如存在）
                for it in node.get("材机列表", []) or []:
                    if isinstance(it, dict):
                        t = it.get("类型") or it.get("type")
                        pj_t = it.get("配件类型")
                        if _type_is_quota_material_equipment(t) or pj_t in ("主材", "配件"):
                            matches.append(
                                {
                                    "来源": "材机列表",
                                    "名称": it.get("项目名称")
                                    or it.get("清单名称")
                                    or it.get("name")
                                    or it.get("材料名称"),
                                    "类型": t,
                                    "配件类型": pj_t,
                                    "id": it.get("GUID") or it.get("guid") or it.get("id"),
                                }
                            )
                return matches

            node_type = quantity_node.get("类型") or quantity_node.get("type")
            if node_type in ("定额", "0"):
                nested_matches = _collect_nested_matches(quantity_node)
                if nested_matches:
                    quantity_node["嵌套检测"] = {
                        "存在嵌套": True,
                        "命中数量": len(nested_matches),
                        "命中项": nested_matches,
                    }

        # 提取人材机节点 - 基于已展开的 resource_node_src 提取
        resources = extract_resource_nodes(resource_node_src)
        if resources:
            # 应用人材机节点类型映射
            resources = [map_resource_node_types(resource) for resource in resources]
            resource_nodes.extend(resources)

            # 从工程量节点中移除材机列表
            if "材机列表" in quantity_node:
                del quantity_node["材机列表"]

        # 清理子节点中的人材机节点
        clean_resource_nodes_from_quantity(quantity_node)

        # 🔥 核心修改：仅当当前节点是"定额"时，才递归处理其children并提取嵌套的工程量节点
        current_node_type = quantity_node.get("类型") or quantity_node.get("type")
        is_quota_node = current_node_type in ("定额", "0")

        sub_quantity_nodes = None
        sub_resource_nodes = None

        if is_quota_node and "children" in quantity_node and quantity_node["children"]:
            # 处理定额的人材机节点
            # 关键：递归输入改为原始副本的 children，避免上一层标准化对子层“原始类型字段”的影响
            sub_quantity_nodes, sub_resource_nodes = process_project_children(
                (original_quantity_node.get("children") or []), software_type
            )

            # 更新当前节点的 children（保留结构）
            if sub_quantity_nodes:
                quantity_node["children"] = sub_quantity_nodes
                # 关键：将子层的工程量节点（如主材/设备）直接并入当前层输出，
                # 并确保类型为标准化后的中文值（主材/设备），避免后续 BCL 过滤不到
                # 不修改原始子节点类型，仅做深拷贝并并入
                normalized_children = [deepcopy(n) for n in sub_quantity_nodes]
                quantity_nodes.extend(normalized_children)
                # 调试：打印并入的子工程量节点类型
                try:
                    for ch in normalized_children:
                        nm = (
                            ch.get("项目名称")
                            or ch.get("清单名称")
                            or ch.get("name")
                            or ch.get("材料名称")
                            or "<未命名>"
                        )
                        tp = ch.get("类型") or ch.get("type")
                        # print(f"[DEBUG] 并入子工程量节点: 名称={nm} | 类型={tp}")
                except Exception:
                    pass
            else:
                quantity_node.pop("children", None)

            # 合并资源节点
            if sub_resource_nodes:
                resource_nodes.extend(sub_resource_nodes)
        # else:
        #   非定额节点：不递归提取子节点作为工程量，不处理 sub_quantity_nodes

        # 对于定额节点：将“材机列表”里符合条件的项（主材/设备或配件类型为主材/配件）也作为工程量节点并入
        if is_quota_node:
            mj_items = (quantity_node.get("材机列表") or []) if isinstance(quantity_node, dict) else []
            for it in mj_items:
                if not isinstance(it, dict):
                    continue
                t = it.get("类型") or it.get("type")
                pj_t = it.get("配件类型")
                # 类型代码转中文
                if t in ("1", "5"):
                    t = {"1": "主材", "5": "设备"}[t]
                new_type = None
                if t in ("主材", "设备"):
                    new_type = t
                elif pj_t == "主材":
                    new_type = "主材"
                elif pj_t == "配件":
                    new_type = "设备"
                if new_type:
                    # 不改写原始字段，直接将条目拷贝加入，类型由下游对象构造派生
                    new_node = deepcopy(it)
                    quantity_nodes.append(new_node)
                    try:
                        nm = (
                            new_node.get("项目名称")
                            or new_node.get("清单名称")
                            or new_node.get("name")
                            or new_node.get("材料名称")
                            or "<未命名>"
                        )
                        # print(f"[DEBUG] 从材机列表并入工程量节点: 名称={nm} | 类型={t} | 派生类型={new_type}")
                    except Exception:
                        pass

        # 🔥 最后：将当前节点本身加入 quantity_nodes
        # 为满足“工程量节点输出保持原样”，此处使用 original_quantity_node 代替处理后的 quantity_node
        # 规则：
        # - 若当前节点为“主材/设备（1/5）”，直接计入工程量节点；
        # - 若为“定额（0）”，沿用既有两个条件之一成立时计入；
        def _children_have_no_children(n: dict) -> bool:
            ch = (n.get("children") or []) if isinstance(n, dict) else []
            # 若无 children，则视作“没有更深层 children”
            if not ch:
                return True
            for c in ch:
                if isinstance(c, dict) and (c.get("children") or []):
                    return False
            return True

        def _mj_has_hit(n: dict) -> bool:
            items = (n.get("材机列表") or []) if isinstance(n, dict) else []
            for it in items:
                if not isinstance(it, dict):
                    continue
                t = it.get("类型") or it.get("type")
                pj_t = it.get("配件类型")
                if t in ("定额", "主材", "设备", "0", "1", "5") or pj_t in ("主材", "配件"):
                    return True
            return False

        # 主材/设备直接计入（以标准化类型入列，避免后续 BCL 过滤不到）
        if current_node_type in ("主材", "设备", "1", "5"):
            to_append = deepcopy(original_quantity_node)
            quantity_nodes.append(to_append)
            try:
                nm = (
                    to_append.get("项目名称")
                    or to_append.get("清单名称")
                    or to_append.get("name")
                    or to_append.get("材料名称")
                    or "<未命名>"
                )
                tp = to_append.get("类型") or to_append.get("type")
                # print(f"[DEBUG] 入列工程量节点(主材/设备): 名称={nm} | 类型={tp}")
            except Exception:
                pass
        # 定额按条件计入
        elif is_quota_node:
            if _children_have_no_children(quantity_node) or _mj_has_hit(quantity_node):
                quantity_nodes.append(original_quantity_node)
                try:
                    nm = (
                        original_quantity_node.get("项目名称")
                        or original_quantity_node.get("清单名称")
                        or original_quantity_node.get("name")
                        or original_quantity_node.get("材料名称")
                        or "<未命名>"
                    )
                    tp = original_quantity_node.get("类型") or original_quantity_node.get("type")
                    # print(f"[DEBUG] 入列工程量节点(定额): 名称={nm} | 类型={tp}")
                except Exception:
                    pass

    # 始终返回列表，避免上层出现 None 导致判定困难
    return (quantity_nodes or [], resource_nodes or [])


def clean_resource_nodes_from_quantity(node):
    """递归清理工程量节点中的人材机节点"""
    if not isinstance(node, dict):
        return

    # 清理材机列表
    if "材机列表" in node:
        del node["材机列表"]

    # 清理子节点中的人材机节点
    if "children" in node and node["children"]:
        # 过滤掉人材机类型的子节点
        node["children"] = [
            c for c in node["children"] if not (isinstance(c, dict) and c.get("类型") in ["人工", "材料", "机械"])
        ]

        # 递归清理剩余子节点
        for child in node["children"]:
            clean_resource_nodes_from_quantity(child)

        # 如果没有子节点了，删除children属性
        if not node["children"]:
            del node["children"]


def load_project_data(json_file_path, project_name, project_guid=None, json_data=None):
    """加载JSON数据并获取目标项目节点"""
    try:
        # 支持透传已加载的 JSON 数据，避免重复读取
        if json_data is not None:
            data = json_data
        else:
            # 读取JSON文件
            with open(json_file_path, "r", encoding="utf-8") as f:
                data = json.load(f)

        # 获取projectData中的costSetting和projectDivision
        project_data = data.get("projectData", {})
        cost_setting = project_data.get("costSetting", {})
        project_division = project_data.get("projectDivision", {})

        # 查找指定项目名称和GUID的节点
        target_nodes = find_project_division_node(project_division, project_name, project_guid)

        if not target_nodes:
            if project_guid:
                print(f"未找到项目名称为 '{project_name}' 且GUID为 '{project_guid}' 的节点")
            else:
                print(f"未找到项目名称为 '{project_name}' 的节点")
            return None, None, None, None, None

        # 获取找到的节点
        target_node = target_nodes[0]

        return data, project_data, cost_setting, project_division, target_node
    except Exception as e:
        print(f"加载项目数据时出错: {e}")
        return None, None, None, None, None


def get_cost_table_children(json_file_path, project_name, project_guid=None, json_data=None):
    """获取取费表子节点"""
    try:
        # 加载项目数据，传递project_guid参数
        data, project_data, cost_setting, project_division, target_node = load_project_data(
            json_file_path, project_name, project_guid, json_data=json_data
        )

        if not target_node:
            return None

        # 获取取费表名称
        fee_table_name = target_node.get("取费表")
        if not fee_table_name:
            print(f"目标项目划分节点中没有'取费表'字段")
            return None

        # 查找对应的取费表
        cost_table = find_cost_table(cost_setting, fee_table_name)
        cost_table_children = cost_table.get("children", None) if cost_table else None

        return cost_table_children
    except Exception as e:
        print(f"获取取费表子节点时出错: {e}")
        return None


def get_quantity_nodes(json_file_path, project_name, engineering_type, project_guid=None, software_type=None):
    """
    获取工程量节点

    Args:
        json_file_path: JSON文件路径
        project_name: 项目名称
        engineering_type: 工程类型
        project_guid: 项目GUID，用于区分同名项目

    Returns:
        list: 工程量节点列表，如果没有找到项目划分节点则返回None，如果找到项目划分节点但没有工程量节点则返回空列表
    """
    try:
        # 加载项目数据，传递project_guid参数
        data, project_data, cost_setting, project_division, target_node = load_project_data(
            json_file_path, project_name, project_guid
        )

        if not target_node:
            return None  # 没有找到项目划分节点

        # 处理项目子节点
        project_children = target_node.get("children", None)
        if not project_children:
            print(f"项目 '{project_name}' (GUID: {project_guid}) 没有子节点（工程量节点）")
            return []  # 找到项目划分节点，但没有子节点（工程量节点）

        # 如果是清单工程，需要先找到清单节点，然后获取其下的工程量节点
        if engineering_type == "清单工程":
            quantity_nodes = []

            print(f"开始处理清单工程，项目名称: {project_name}")

            # 递归查找真正的清单节点
            def find_true_bill_nodes(node_list, parent_path=""):
                result = []
                for node in node_list:
                    # 检查是否是清单节点
                    is_bill_node = (
                        node.get("类型") == "8"
                        or node.get("类型") == "清单"
                        or node.get("type") == "8"
                        or node.get("type") == "清单"
                        or "清单名称" in node
                        or "清单全码" in node
                    )

                    node_name = node.get("清单名称", node.get("项目名称", "未命名"))
                    # 强制使用GUID而不是ID
                    node_id = node.get("GUID") or node.get("guid") or node.get("id", "未知ID")
                    current_path = f"{parent_path}/{node_name}" if parent_path else node_name

                    if is_bill_node:
                        print(f"找到清单节点: ID={node_id}, 名称={node_name}, 路径={current_path}")
                        # 检查清单节点是否有取费表名称
                        has_fee_table = "取费表名称" in node or "取费表" in node
                        if has_fee_table:
                            fee_table_name = node.get("取费表名称", node.get("取费表", "未知"))
                            print(f"清单节点有取费表: {fee_table_name}")
                            # 添加到缓存
                            if node_id != "未知ID":
                                _bill_node_cache[node_id] = node
                                print(f"缓存清单节点: ID={node_id}")
                            result.append(node)
                        else:
                            print(f"警告：清单节点没有取费表名称: {node_name}")

                    # 递归处理子节点
                    if "children" in node and node["children"]:
                        result.extend(find_true_bill_nodes(node["children"], current_path))

                return result

            # 查找所有真正的清单节点
            bill_nodes = find_true_bill_nodes(project_children)
            print(f"找到 {len(bill_nodes)} 个有效清单节点")

            # 处理每个清单节点
            for bill_node in bill_nodes:
                # 强制使用GUID而不是ID
                bill_id = bill_node.get("GUID") or bill_node.get("guid") or bill_node.get("id")
                bill_name = bill_node.get("清单名称", bill_node.get("项目名称", "未命名清单"))
                bill_children = bill_node.get("children", [])

                if bill_children:
                    # 处理清单节点的子节点，获取工程量节点
                    bill_quantity_nodes, _ = process_project_children(bill_children, software_type)

                    if bill_quantity_nodes:
                        print(f"清单节点 '{bill_name}' 下找到 {len(bill_quantity_nodes)} 个工程量节点")

                        # 为每个工程量节点设置清单节点信息
                        for node in bill_quantity_nodes:
                            # 存储关键信息而不是整个对象
                            # 强制使用GUID而不是ID
                            node["bill_guid"] = bill_id  # 新增GUID字段
                            node["bill_id"] = bill_id  # 保持兼容性
                            node["bill_name"] = bill_name
                            node["取费表名称"] = bill_node.get("取费表名称", bill_node.get("取费表", ""))
                            # 设置parent_id以保持兼容性
                            node["parent_id"] = bill_id
                            print(f"设置工程量节点 '{node.get('项目名称', '未命名')}' 的清单节点信息")

                            # 添加这一行，将完整的清单节点添加到工程量节点中
                            node["bill_node"] = bill_node

                        quantity_nodes.extend(bill_quantity_nodes)
                    else:
                        print(f"清单节点 '{bill_name}' 下未找到工程量节点")
                else:
                    print(f"清单节点 '{bill_name}' 没有子节点")

            # 如果没有找到任何工程量节点，尝试直接获取定额节点
            if not quantity_nodes:
                print("未找到清单节点下的工程量节点，尝试直接获取定额节点...")

                def find_quota_nodes(node_list):
                    result = []
                    for node in node_list:
                        # 检查是否是定额节点
                        is_quota_node = node.get("类型") == "定额" or node.get("type") == "定额"

                        if is_quota_node:
                            result.append(node)

                        # 递归处理子节点
                        if "children" in node and node["children"]:
                            result.extend(find_quota_nodes(node["children"]))

                    return result

                # 查找所有定额节点
                quota_nodes = find_quota_nodes(project_children)
                if quota_nodes:
                    print(f"找到 {len(quota_nodes)} 个定额节点")

                    # 检查这些定额节点是否有清单节点信息
                    for node in quota_nodes:
                        parent_node = None
                        parent_id = node.get("parent_id")

                        # 在项目中查找父节点
                        def find_node_by_id(node_list, node_id):
                            for n in node_list:
                                # 强制使用GUID而不是ID进行匹配
                                node_guid = n.get("GUID") or n.get("guid") or n.get("id")
                                if node_guid == node_id:
                                    return n
                                if "children" in n and n["children"]:
                                    found = find_node_by_id(n["children"], node_id)
                                    if found:
                                        return found
                            return None

                        if parent_id:
                            parent_node = find_node_by_id(project_children, parent_id)

                        # 如果找到父节点且是清单节点，使用其信息
                        if parent_node:
                            is_bill_node = (
                                parent_node.get("类型") == "8"
                                or parent_node.get("类型") == "清单"
                                or parent_node.get("type") == "8"
                                or parent_node.get("type") == "清单"
                                or "清单名称" in parent_node
                                or "清单全码" in parent_node
                            )

                            if is_bill_node:
                                # 强制使用GUID而不是ID
                                bill_id = parent_node.get("GUID") or parent_node.get("guid") or parent_node.get("id")
                                bill_name = parent_node.get("清单名称", parent_node.get("项目名称", "未命名清单"))

                                # 强制使用GUID而不是ID
                                node["bill_guid"] = bill_id  # 新增GUID字段
                                node["bill_id"] = bill_id  # 保持兼容性
                                node["bill_name"] = bill_name
                                node["取费表名称"] = parent_node.get("取费表名称", parent_node.get("取费表", ""))
                                # 设置parent_id以保持兼容性
                                node["parent_id"] = bill_id
                                print(f"为定额节点 '{node.get('项目名称', '未命名')}' 设置清单节点信息")

                    quantity_nodes.extend(quota_nodes)

            return quantity_nodes
        else:
            # 预算工程 - 直接获取工程量节点
            quantity_nodes, _ = process_project_children(project_children, software_type)

            # 如果没有工程量节点，返回空列表而不是None
            if not quantity_nodes:
                print(f"项目 '{project_name}' (GUID: {project_guid}) 没有工程量节点")
                return []

            return quantity_nodes

    except Exception as e:
        print(f"获取工程量节点时出错: {e}")
        import traceback

        traceback.print_exc()  # 打印详细错误堆栈
        return None


def get_resource_nodes(json_file_path, project_name, project_guid=None):
    """
    获取人材机节点

    Args:
        json_file_path: JSON文件路径
        project_name: 项目名称
        project_guid: 项目GUID，用于区分同名项目

    Returns:
        list: 人材机节点列表
    """
    try:
        # 加载项目数据，传递project_guid参数
        data, project_data, cost_setting, project_division, target_node = load_project_data(
            json_file_path, project_name, project_guid
        )

        if not target_node:
            return None

        # 获取项目子节点
        project_children = target_node.get("children", None)
        if not project_children:
            print(f"项目 '{project_name}' (GUID: {project_guid}) 没有子节点")
            return []

        # 提取人材机节点
        resource_nodes = []
        for child in project_children:
            # 提取人材机节点并保持父子结构
            nodes = extract_resource_nodes(child)
            if nodes:
                resource_nodes.extend(nodes)

        # 映射节点类型
        resource_nodes = [map_resource_node_types(node) for node in resource_nodes]

        return resource_nodes
    except Exception as e:
        print(f"获取人材机节点时出错: {e}")
        return None


def get_classified_resource_nodes(json_file_path, project_name, project_guid=None):
    """
    获取分类后的人材机节点

    Args:
        json_file_path: JSON文件路径
        project_name: 项目名称
        project_guid: 项目GUID，用于区分同名项目

    Returns:
        tuple: (人工节点列表, 材料节点列表, 机械节点列表)，每个列表包含(节点, 父级ID)元组
    """
    # 获取所有人材机节点，传递project_guid参数
    resource_nodes = get_resource_nodes(json_file_path, project_name, project_guid)
    if not resource_nodes:
        return [], [], []

    # 分类存储
    labor_nodes = []  # 人工节点
    material_nodes = []  # 材料节点
    machine_nodes = []  # 机械节点

    # 递归函数，用于处理节点及其子节点
    def process_node(node):
        node_type = node.get("类型")
        parent_id = node.get("parent_id")

        # 同时支持数字类型和字符串类型
        if node_type in ["人工", "2"]:
            node_type = "人工"  # 统一转换为字符串类型
            labor_nodes.append((node, parent_id))
        elif node_type in ["材料", "3"]:
            node_type = "材料"  # 统一转换为字符串类型
            material_nodes.append((node, parent_id))
        elif node_type in ["机械", "4"]:
            node_type = "机械"  # 统一转换为字符串类型
            machine_nodes.append((node, parent_id))

        # 更新类型字段
        node["类型"] = node_type

        # 处理子节点
        if "children" in node and node["children"]:
            for child in node["children"]:
                # 确保子节点有父级ID
                if "parent_id" not in child:
                    child["parent_id"] = node.get("id")
                process_node(child)

    # 处理所有节点
    for node in resource_nodes:
        process_node(node)

    return labor_nodes, material_nodes, machine_nodes


def find_bill_node_by_id(node, bill_id, result=None):
    """递归查找指定ID的清单节点"""
    if result is not None and result:
        return result

    if isinstance(node, dict):
        # 检查当前节点是否是目标清单节点
        node_id_matches = node.get("id") == bill_id or node.get("GUID") == bill_id

        if node_id_matches:
            # 检查是否是清单节点
            is_bill_node = (
                node.get("类型") == "8"
                or node.get("类型") == "清单"
                or node.get("type") == "8"
                or node.get("type") == "清单"
                or "清单名称" in node
                or "清单全码" in node
            )

            if is_bill_node:
                print(f"找到清单节点 ID={bill_id}, 类型={node.get('类型', node.get('type', '未知'))}")
                return [node]
            else:
                print(
                    f"找到ID匹配但不是清单节点的节点: ID={bill_id}, 类型={node.get('类型', node.get('type', '未知'))}"
                )
                # 不返回非清单节点

        # 递归查找子节点
        if "children" in node and isinstance(node["children"], list):
            for child in node["children"]:
                result = find_bill_node_by_id(child, bill_id, result)
                if result:
                    return result

        # 检查其他字段
        for key, value in node.items():
            if key != "children" and isinstance(value, (dict, list)):
                result = find_bill_node_by_id(value, bill_id, result)
                if result:
                    return result

    elif isinstance(node, list):
        for item in node:
            if isinstance(item, (dict, list)):
                result = find_bill_node_by_id(item, bill_id, result)
                if result:
                    return result

    return result or []


def get_bill_cost_table(json_file_path, bill_id):
    """获取清单节点的取费表子节点"""
    try:
        # 读取JSON文件
        with open(json_file_path, "r", encoding="utf-8") as f:
            data = json.load(f)

        # 获取projectData中的costSetting和projectDivision
        project_data = data.get("projectData", {})
        cost_setting = project_data.get("costSetting", {})
        project_division = project_data.get("projectDivision", {})

        print(f"正在查找清单节点ID: {bill_id}")

        # 查找指定ID的清单节点
        bill_nodes = find_bill_node_by_id(project_division, bill_id)

        if not bill_nodes:
            print(f"未找到ID为 '{bill_id}' 的清单节点")
            return None

        # 获取找到的节点
        bill_node = bill_nodes[0]

        # 获取取费表名称或ID - 尝试多种可能的字段名
        fee_table_name = bill_node.get("取费表名称") or bill_node.get("取费表") or bill_node.get("费率表")

        if not fee_table_name:
            print(f"清单节点中没有'取费表名称'字段")
            # 打印节点信息以便调试
            print(f"清单节点字段: {', '.join(bill_node.keys())}")

            # 尝试从父级节点获取取费表名称
            if "parent_id" in bill_node:
                parent_nodes = find_bill_node_by_id(project_division, bill_node["parent_id"])
                if parent_nodes:
                    parent_node = parent_nodes[0]
                    fee_table_name = (
                        parent_node.get("取费表名称") or parent_node.get("取费表") or parent_node.get("费率表")
                    )
                    if fee_table_name:
                        print(f"从父级节点获取到取费表名称: {fee_table_name}")

            if not fee_table_name:
                return None

        print(f"找到取费表名称: {fee_table_name}")

        # 使用相同的函数查找取费表 - 与预算工程保持一致
        cost_table = find_cost_table(cost_setting, fee_table_name)

        if not cost_table:
            print(f"未找到取费表 '{fee_table_name}'")
            return None

        cost_table_children = cost_table.get("children", None)
        return cost_table_children

    except Exception as e:
        print(f"获取清单节点取费表时出错: {e}")
        import traceback

        traceback.print_exc()  # 打印详细错误堆栈
        return None


def get_bill_node_by_id(bill_id: str) -> Dict[str, Any]:
    """
    根据ID从缓存中获取清单节点

    Args:
        bill_id: 清单节点ID

    Returns:
        Dict[str, Any]: 清单节点数据
    """
    # 清理ID格式
    clean_bill_id = str(bill_id).strip("{}").upper()

    # 在缓存中查找
    for cached_id, node in _bill_node_cache.items():
        if str(cached_id).strip("{}").upper() == clean_bill_id:
            print(f"从缓存中获取清单节点: {node.get('清单名称', '未命名')}")
            return node

    print(f"在缓存中未找到ID为 {bill_id} 的清单节点")
    return {}


# if __name__ == "__main__":
#     # 测试参数（使用新合并的JSON文件）
#     json_file_path = "project2json/outputs/merged/变电检修国网-副本.json"
#     project_name = "1.12新增卸车保管"
#     project_guid = "{0952D46D-E5C7-4412-88FF-93517EF2633C}"

#     def _node_name(n: dict):
#         # 尽可能稳健地取名称字段
#         for k in ("名称", "name", "材料名称", "定额名称", "项目名称", "title"):
#             if isinstance(n, dict) and n.get(k):
#                 return n.get(k)
#         return n.get("newGuid") or n.get("GUID") or n.get("id") or "<无名称>"

#     def _node_type(n: dict):
#         return n.get("类型") or n.get("type") or "<无类型>"

#     def _node_id(n: dict):
#         return n.get("GUID") or n.get("guid") or n.get("id") or n.get("parent_id") or "<无ID>"

#     def _print_nodes(nodes, title):
#         print(f"{title} 共 {len(nodes) if nodes else 0} 个：")
#         if not nodes:
#             return
#         for i, item in enumerate(nodes, 1):
#             n = item[0] if isinstance(item, tuple) and item and isinstance(item[0], dict) else item
#             try:
#                 print(f"  {i:02d}. 名称={_node_name(n)} | 类型={_node_type(n)} | ID={_node_id(n)}")
#             except Exception:
#                 print(f"  {i:02d}. {item}")

#     def _check_resource_parents_with_children(nodes):
#         """统计是否存在类型为人/材/机且仍带children的父节点"""

#         def _is_res(t):
#             return t in ("人工", "材料", "机械", "2", "3", "4")

#         bad = []
#         for item in nodes or []:
#             n = item[0] if isinstance(item, tuple) and item and isinstance(item[0], dict) else item
#             if isinstance(n, dict) and _is_res(n.get("类型") or n.get("type")) and n.get("children"):
#                 bad.append(n)
#         if bad:
#             print("[警告] 发现仍存在带children的人材机父节点，这些父节点应被跳过，子节点应上提：")
#             _print_nodes(bad, "异常父级人材机节点")
#         else:
#             print("[校验] 未发现带children的人材机父节点，扁平化正常。")

#     print(f"测试项目: {project_name}")
#     print(f"文件路径: {json_file_path}")
#     print(f"项目GUID: {project_guid}")
#     print("-" * 50)

#     # 获取工程量节点
#     print("获取工程量节点...")
#     quantity_nodes = get_quantity_nodes(json_file_path, project_name, "预算工程", project_guid, software_type="技改")
#     _print_nodes(quantity_nodes or [], "工程量节点")

#     # 获取人材机节点（应为：父为人材机且有children时，父不计，子上提并继承父类型）
#     print("获取人材机节点...")
#     resource_nodes = get_resource_nodes(json_file_path, project_name, project_guid)
#     _print_nodes(resource_nodes or [], "人材机节点")
#     _check_resource_parents_with_children(resource_nodes or [])

#     # 获取分类后的人材机节点
#     print("获取分类后的人材机节点...")
#     labor_nodes, material_nodes, machinery_nodes = get_classified_resource_nodes(
#         json_file_path, project_name, project_guid
#     )
#     _print_nodes(labor_nodes or [], "人工节点")
#     _print_nodes(material_nodes or [], "材料节点")
#     _print_nodes(machinery_nodes or [], "机械节点")

#     print("-" * 50)
#     print("测试完成!")
