"""
结果格式化模块

功能：从 Neo4j 查询结果中提取节点和路径信息
"""

import logging
import re
from typing import Dict, List, Any, Optional, Set
logger = logging.getLogger(__name__)

EXCLUDED_RESULT_NODE_LABELS = {"维修工作", "操作程序", "操作使用"}
_FILTERED_OUT = object()


class SimpleNode:
    """
    简单的Node模拟类，用于将节点数据包装为Neo4j Node对象格式（降级策略）
    """
    def __init__(self, node_data: dict, labels: List[str], node_id: str):
        """
        Args:
            node_data: 节点数据字典
            labels: 节点标签列表
            node_id: 节点ID
        """
        self._data = node_data
        self._labels = labels
        self._node_id = node_id

    @property
    def element_id(self):
        """返回节点ID"""
        return self._node_id

    @property
    def id(self):
        """返回节点ID（兼容旧版本）"""
        return self._node_id

    @property
    def labels(self):
        """返回节点标签"""
        return self._labels

    def get(self, key, default=None):
        """获取节点属性"""
        return self._data.get(key, default)

    def __iter__(self):
        """使节点可以转换为字典"""
        return iter(self._data.items())

    def __getitem__(self, key):
        """使节点可以像字典一样访问"""
        return self._data[key]


class SimplePath:
    """
    简单的Path模拟类，用于将节点包装为path结构（降级策略）
    包含nodes和relationships属性，其中relationships为空列表
    """
    def __init__(self, nodes: List):
        """
        Args:
            nodes: 节点列表（SimpleNode对象列表）
        """
        self.nodes = nodes
        self.relationships = []  # 降级策略中，只有节点，没有关系


# def extract_knowledge_source(knowledge_source_value: Any) -> List[Dict[str, str]]:
#     """
#     从节点属性中的 knowledge_source 字段提取信息并格式化为标准格式
# 
#     Args:
#         knowledge_source_value: 节点属性中的 knowledge_source 值（可能是字符串、字典、列表等）
# 
#     Returns:
#         List[Dict]: 格式化后的 knowledge_source 列表，格式为 [{"filename":"","url":"","info":""}]
#     """
#     if knowledge_source_value is None:
#         return [{"filename": "", "url": "", "info": ""}]
# 
#     result = []
# 
#     # 如果是字符串，尝试解析为JSON或直接使用
#     if isinstance(knowledge_source_value, str):
#         # 尝试解析JSON字符串
#         try:
#             import json
#             parsed = json.loads(knowledge_source_value)
#             if isinstance(parsed, list):
#                 knowledge_source_value = parsed
#             elif isinstance(parsed, dict):
#                 knowledge_source_value = [parsed]
#             else:
#                 # 如果不是JSON，将字符串作为info
#                 return [{"filename": "", "url": "", "info": knowledge_source_value}]
#         except:
#             # 解析失败，将字符串作为info
#             return [{"filename": "", "url": "", "info": knowledge_source_value}]
# 
#     # 如果是字典，转换为列表
#     if isinstance(knowledge_source_value, dict):
#         knowledge_source_value = [knowledge_source_value]
# 
#     # 如果是列表，处理每个元素
#     if isinstance(knowledge_source_value, list):
#         for item in knowledge_source_value:
#             if isinstance(item, dict):
#                 # 从字典中提取字段
#                 formatted_item = {
#                     "filename": str(item.get("filename", item.get("file_name", item.get("file", "")))),
#                     "url": str(item.get("url", item.get("link", item.get("uri", "")))),
#                     "info": str(item.get("info", item.get("information", item.get("description", ""))))
#                 }
#                 result.append(formatted_item)
#             elif isinstance(item, str):
#                 # 如果是字符串，作为info
#                 result.append({"filename": "", "url": "", "info": item})
#             else:
#                 # 其他类型，转换为字符串作为info
#                 result.append({"filename": "", "url": "", "info": str(item)})
#     else:
#         # 其他类型，转换为字符串作为info
#         result.append({"filename": "", "url": "", "info": str(knowledge_source_value)})
# 
#     # 如果没有结果，返回默认值
#     if not result:
#         result = [{"filename": "", "url": "", "info": ""}]
# 
#     return result
def extract_knowledge_source(knowledge_source_value: Any) -> List[Dict[str, str]]:
    """
    从节点属性中的 knowledge_source 字段提取信息并格式化为标准格式（不含 info 字段）

    Args:
        knowledge_source_value: 节点属性中的 knowledge_source 值（可能是字符串、字典、列表等）

    Returns:
        List[Dict]: 格式化后的 knowledge_source 列表，格式为 [{"filename":"","url":""}]
    """
    if knowledge_source_value is None:
        return [{"filename": "", "url": ""}]

    result = []

    # 如果是字符串，尝试解析为JSON或直接使用
    if isinstance(knowledge_source_value, str):
        # 尝试解析JSON字符串
        try:
            import json
            parsed = json.loads(knowledge_source_value)
            if isinstance(parsed, list):
                knowledge_source_value = parsed
            elif isinstance(parsed, dict):
                knowledge_source_value = [parsed]
            else:
                # 如果不是JSON，跳过该字符串
                return [{"filename": "", "url": ""}]
        except:
            # 解析失败，跳过该字符串
            return [{"filename": "", "url": ""}]

    # 如果是字典，转换为列表
    if isinstance(knowledge_source_value, dict):
        knowledge_source_value = [knowledge_source_value]

    # 如果是列表，处理每个元素
    if isinstance(knowledge_source_value, list):
        for item in knowledge_source_value:
            if isinstance(item, dict):
                # 从字典中提取字段（不含 info）
                formatted_item = {
                    "filename": str(item.get("filename", item.get("file_name", item.get("file", "")))),
                    "url": str(item.get("url", item.get("link", item.get("uri", ""))))
                }
                result.append(formatted_item)
            elif isinstance(item, str):
                # 如果是字符串，跳过
                continue
            else:
                # 其他类型，跳过
                continue
    else:
        # 其他类型，跳过
        return [{"filename": "", "url": ""}]

    # 如果没有结果，返回默认值
    if not result:
        result = [{"filename": "", "url": ""}]

    return result

def remove_sensitive_fields(data: Any) -> Any:
    """
    递归删除数据中的 embedding、fulltext 和 path 字段
    参考 test.py 的实现方式：在转换为 dict 后立即 pop 掉敏感字段

    Args:
        data: 要处理的数据（可以是字典、列表、Neo4j对象或基本类型）

    Returns:
        清理后的数据
    """
    # 如果是 Neo4j Node 对象，转换为字典并删除敏感字段（参考 test.py）
    if hasattr(data, 'labels') and (hasattr(data, 'element_id') or hasattr(data, 'id')):
        try:
            props = dict(data)
            props.pop('embedding', None)
            props.pop('fulltext', None)
            # 递归处理属性中的嵌套结构
            return remove_sensitive_fields(props)
        except:
            return data

    # 如果是 Neo4j Relationship 对象，转换为字典并删除敏感字段
    if hasattr(data, 'type') and (hasattr(data, 'element_id') or hasattr(data, 'id')):
        try:
            props = dict(data)
            props.pop('embedding', None)
            props.pop('fulltext', None)
            # 递归处理属性中的嵌套结构
            return remove_sensitive_fields(props)
        except:
            return data

    if isinstance(data, dict):
        # 创建新字典，排除 embedding、fulltext 和 path
        result = {}
        for key, value in data.items():
            if key not in ['embedding', 'fulltext', 'path']:
                # 递归处理嵌套的字典、列表和 Neo4j 对象
                result[key] = remove_sensitive_fields(value)
        return result
    elif isinstance(data, list):
        # 递归处理列表中的每个元素
        return [remove_sensitive_fields(item) for item in data]
    else:
        # 基本类型直接返回
        return data

def format_results(results: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    格式化图谱查询结果，提取所有节点和路径（自动去重）

    从 rerank 后的结果字典列表中提取 path 信息，生成 nodes 和 links

    Args:
        results: rerank 后的结果列表（字典列表，每个字典包含 rerank_score 和 path 等信息）

    Returns:
        Dict: 包含 nodes 和 links 的字典（已去重，只保留最高分）
    """
    if not results:
        return {"nodes": [], "links": []}

    all_nodes = {}  # key: node_id(str), value: node dict
    all_links = {}  # key: rel_id(str), value: link dict
    node_scores = {}  # key: node_id(str), value: highest rerank score for the node

    def process_path(path, current_score):
        """处理单个 Path 对象"""
        if path is None:
            return

        def process_node(node):
            """处理单个节点的辅助函数"""
            if node is None:
                return

            node_id = str(node.element_id) if hasattr(node, 'element_id') else str(node.id)

            if node_id not in all_nodes:
                props = dict(node)
                props.pop('embedding', None)
                props.pop('fulltext', None)
                props.pop('切片', None)

                # 提取 name 用于外部字段（从props中提取后移除，避免出现在properties中）
                node_name = props.pop('name', None) or node.get("名称", f"Node_{node_id}")

                # 直接过滤掉 last_updated 和 created_at
                props.pop('last_updated', None)
                props.pop('created_at', None)

                # 提取并格式化 knowledge_source（从props中提取后移除原始值）
                knowledge_source_value = props.pop('knowledge_source', None)
                formatted_knowledge_source = extract_knowledge_source(knowledge_source_value)

                all_nodes[node_id] = {
                    "id": node_id,
                    "name": node_name,
                    "labels": list(node.labels),
                    "properties": props,
                    "knowledge_source": formatted_knowledge_source
                }
                # 初始化节点的最高分数
                if current_score > 0:
                    node_scores[node_id] = current_score
            else:
                # 如果节点已存在，比较并保留最高分数
                existing_score = node_scores.get(node_id, 0)
                if current_score > existing_score:
                    node_scores[node_id] = current_score

        try:
            # 处理节点：优先使用 path.nodes，如果为空则使用 start/end 或 start_node/end_node
            nodes_processed = False

            # 首先尝试使用 path.nodes
            if hasattr(path, 'nodes'):
                try:
                    # 直接迭代 path.nodes（可能是生成器）
                    for node in path.nodes:
                        process_node(node)
                        nodes_processed = True
                except Exception as e:
                    logger.debug(f"迭代 path.nodes 时出错: {e}")

            # 如果 path.nodes 没有处理任何节点（size=0的情况），尝试使用 start/end 节点
            if not nodes_processed:
                start_node = None
                end_node = None

                # 尝试多种方式获取起始节点（优先使用 start_node，其次使用 start）
                if hasattr(path, 'start_node'):
                    try:
                        start_node = path.start_node
                    except Exception:
                        pass
                elif hasattr(path, 'start'):
                    try:
                        start_node = path.start
                    except Exception:
                        pass

                # 尝试多种方式获取结束节点（优先使用 end_node，其次使用 end）
                if hasattr(path, 'end_node'):
                    try:
                        end_node = path.end_node
                    except Exception:
                        pass
                elif hasattr(path, 'end'):
                    try:
                        end_node = path.end
                    except Exception:
                        pass

                # 处理起始节点
                if start_node is not None:
                    process_node(start_node)

                # 处理结束节点（只有当与起始节点不同时才处理，避免重复）
                if end_node is not None and (start_node is None or end_node != start_node):
                    process_node(end_node)

            # 处理关系
            for rel in path.relationships:
                rel_id = str(rel.element_id) if hasattr(rel, 'element_id') else str(rel.id)

                if rel_id not in all_links:
                    rel_props = dict(rel)
                    rel_props.pop('embedding', None)
                    rel_props.pop('fulltext', None)

                    start_node_id = str(rel.start_node.element_id) if hasattr(rel.start_node, 'element_id') else str(rel.start_node.id)
                    end_node_id = str(rel.end_node.element_id) if hasattr(rel.end_node, 'element_id') else str(rel.end_node.id)

                    all_links[rel_id] = {
                        "id": rel_id,
                        "label": rel.type,
                        "source": start_node_id,
                        "target": end_node_id,
                        "properties": rel_props
                    }
        except Exception as e:
            logger.warning(f"处理 Path 对象时出错: {e}", exc_info=True)

    for result in results:
        try:
            # 获取当前记录的rerank_score
            current_rerank_score = result.get('rerank_score', 0)

            # 查找 path 字段（可能是 path 或 paths）
            path_value = None
            if 'path' in result:
                path_value = result['path']
            elif 'paths' in result:
                path_value = result['paths']
            else:
                # 尝试从所有值中查找 Path 对象
                for key, value in result.items():
                    if value is not None and hasattr(value, 'nodes') and hasattr(value, 'relationships'):
                        path_value = value
                        break

            # 处理 path_value
            if path_value is not None:
                # 处理单个 Path 对象
                if hasattr(path_value, 'nodes') and hasattr(path_value, 'relationships'):
                    process_path(path_value, current_rerank_score)
                # 处理路径列表
                elif isinstance(path_value, list):
                    if path_value:  # 列表不为空
                        for path in path_value:
                            if path is not None and hasattr(path, 'nodes') and hasattr(path, 'relationships'):
                                process_path(path, current_rerank_score)
        except Exception as e:
            logger.warning(f"格式化结果记录失败: {e}", exc_info=True)

    # 为每个节点添加最高rerank分数到其属性中（只保留高分）
    for node_id, score in node_scores.items():
        if node_id in all_nodes:
            node = all_nodes[node_id]
            if 'properties' not in node:
                node['properties'] = {}
            # 只保留最高分，使用 分数 作为 key
            node['properties']['分数'] = score

    # 最终清理：确保所有嵌套数据中的敏感字段都被删除
    cleaned_nodes = [remove_sensitive_fields(node) for node in all_nodes.values()]
    cleaned_links = [remove_sensitive_fields(link) for link in all_links.values()]

    return {
        "nodes": cleaned_nodes,
        "links": cleaned_links
    }


def format_entry_nodes_as_results(entry_nodes: dict) -> List[Dict[str, Any]]:
    """
    将入口节点信息格式化为检索结果（降级策略）
    将节点作为path，节点作为结果

    Args:
        entry_nodes: 入口节点字典，格式如 {"设备": [{"name": "xxx", "score": 0.9, "node": {...}}]}

    Returns:
        List[Dict]: 结果列表，每个元素是一个结果记录，包含path字段（用于 graph_retrieval.py 的降级策略）
    """
    results = []

    if not entry_nodes:
        return results

    for label, nodes in entry_nodes.items():
        if not isinstance(nodes, list) or not nodes:
            continue

        # 标签列表（优先使用entry_nodes中的label，如果节点数据中有labels则使用节点数据的labels）
        label_list = [label] if label else []

        for node_info in nodes:
            node_name = node_info.get("name") or node_info.get("_name") or (node_info.get("node", {}).get("名称")) or str(node_info.get("node", ""))
            node_data = node_info.get("node", {})
            node_score = node_info.get("score", 0.8)

            # 如果node_data是Neo4j Node对象，转换为字典
            if hasattr(node_data, 'labels') and (hasattr(node_data, 'element_id') or hasattr(node_data, 'id')):
                # 从Neo4j Node对象提取labels
                node_labels = list(node_data.labels) if hasattr(node_data, 'labels') else label_list
                if node_labels:
                    label_list = node_labels
                # 转换为字典
                node_data = dict(node_data)

            # 跳过无效节点
            if not isinstance(node_data, dict) and not node_name:
                continue

            # 提取节点ID（兜底情况下使用简单的ID格式）
            if isinstance(node_data, dict):
                # 优先使用element_id或id（Neo4j原生ID）
                if node_data.get("element_id"):
                    node_id = str(node_data.get("element_id"))
                elif node_data.get("id"):
                    node_id = str(node_data.get("id"))
                else:
                    # 兜底情况：使用节点的名称属性作为简单ID
                    # 优先使用中文属性名"名称"，其次使用"name"
                    node_id = node_data.get("名称") or node_data.get("name") or node_name
                    if node_id:
                        node_id = str(node_id)
                    else:
                        # 如果都没有，生成一个简单的ID
                        node_id = f"{label}_{len(results)}"
            else:
                node_id = str(node_name) if node_name else f"{label}_{len(results)}"

            # 提取节点属性（排除内部字段和指定字段）
            node_properties = {}
            if isinstance(node_data, dict):
                for key, value in node_data.items():
                    if key not in ["element_id", "id", "labels", "embedding", "fulltext", "name", "切片"]:
                        node_properties[key] = value

                # 如果节点数据中有labels，使用它
                if "labels" in node_data and node_data["labels"]:
                    if isinstance(node_data["labels"], list):
                        label_list = node_data["labels"]
                    elif hasattr(node_data["labels"], '__iter__'):
                        label_list = list(node_data["labels"])

            # 如果没有属性，跳过
            if not node_properties:
                continue

            # 直接过滤掉 last_updated 和 created_at
            node_properties.pop('last_updated', None)
            node_properties.pop('created_at', None)

            # 使用remove_sensitive_fields递归删除embedding和fulltext字段（确保嵌套结构也被清理）
            node_properties = remove_sensitive_fields(node_properties)

            # 提取并格式化 knowledge_source
            knowledge_source_value = node_properties.get('knowledge_source')
            formatted_knowledge_source = extract_knowledge_source(knowledge_source_value)

            # 创建SimpleNode对象（knowledge_source 会在 format_results 的 process_node 中再次处理）
            simple_node = SimpleNode(
                node_data=node_properties,
                labels=label_list,
                node_id=node_id
            )

            # 创建SimplePath对象（只包含一个节点）
            simple_path = SimplePath(nodes=[simple_node])

            # 构建结果字典
            # 包含path字段（节点作为path），同时将节点属性作为result字段
            result = {
                "path": simple_path,
                "result": node_properties  # 节点作为结果（已删除embedding和fulltext）
            }

            results.append(result)

    return results


def _normalize_label_values(labels: Any) -> Set[str]:
    """将 labels/标签/type 等字段统一成标签集合。"""
    if labels is None:
        return set()

    if isinstance(labels, str):
        parts = re.split(r"[,，、/\s]+", labels)
        return {part.strip("[]'\" ") for part in parts if part.strip("[]'\" ")}

    if isinstance(labels, (list, tuple, set, frozenset)):
        return {str(label).strip() for label in labels if str(label).strip()}

    return {str(labels).strip()} if str(labels).strip() else set()


def _dict_label_values(data: Dict[str, Any]) -> Set[str]:
    label_keys = ("labels", "标签", "label", "类型", "type", "节点类型", "node_type")
    labels = set()
    for key in label_keys:
        if key in data:
            labels.update(_normalize_label_values(data.get(key)))
    return labels


def _has_excluded_result_label(value: Any) -> bool:
    """判断一个结果对象是否明确属于需要从 results 隐藏的节点类型。"""
    labels = set()

    if isinstance(value, dict):
        labels.update(_dict_label_values(value))
    elif hasattr(value, "labels") and (hasattr(value, "element_id") or hasattr(value, "id")):
        try:
            labels.update(_normalize_label_values(value.labels))
        except Exception:
            pass

    return bool(labels & EXCLUDED_RESULT_NODE_LABELS)


def _mentions_excluded_result_label(text: Any) -> bool:
    if text is None:
        return False
    return any(label in str(text) for label in EXCLUDED_RESULT_NODE_LABELS)


def _is_empty_result_content(value: Any) -> bool:
    return value in (None, {}, [])


def _filter_excluded_result_content(value: Any, parent_key: Any = None) -> Any:
    """
    只用于 results 字段：过滤掉维修工作、操作程序、操作使用这类节点内容。
    nodes/links 的原始返回不会经过这个函数。
    """
    if parent_key is not None and _mentions_excluded_result_label(parent_key):
        return _FILTERED_OUT

    if _has_excluded_result_label(value):
        return _FILTERED_OUT

    if isinstance(value, dict):
        name_value = value.get("名称", value.get("name"))
        if _mentions_excluded_result_label(name_value):
            return _FILTERED_OUT

        filtered = {}
        for key, child in value.items():
            if _mentions_excluded_result_label(key):
                continue
            filtered_child = _filter_excluded_result_content(child, key)
            if filtered_child is not _FILTERED_OUT:
                filtered[key] = filtered_child

        return _FILTERED_OUT if _is_empty_result_content(filtered) else filtered

    if isinstance(value, list):
        filtered_items = []
        for item in value:
            filtered_item = _filter_excluded_result_content(item)
            if filtered_item is not _FILTERED_OUT and not _is_empty_result_content(filtered_item):
                filtered_items.append(filtered_item)
        return filtered_items

    return value


def _filter_results_for_output(results: Any) -> List[Any]:
    filtered = _filter_excluded_result_content(results)
    if filtered is _FILTERED_OUT or _is_empty_result_content(filtered):
        return []
    if isinstance(filtered, list):
        return filtered
    return [filtered]


def _filter_result_nodes_for_output(nodes: Any) -> List[Dict[str, Any]]:
    if not isinstance(nodes, list):
        return []
    return [
        node for node in nodes
        if isinstance(node, dict) and not _has_excluded_result_label(node)
    ]



def _format_value(value: Any) -> str:
    """
    将任意值转为简洁字符串，自适应处理：
    - 数字或字符串：直接使用
    - 字典：检查是否有 result 字段，分解 result 字段，去除 knowledge_source
    - 列表：递归处理每个元素
    """
    # 如果直接是数字或字符串，直接返回
    if isinstance(value, (str, int, float)):
        return str(value)

    # 如果是字典，检查是否有 result 字段
    elif isinstance(value, dict):
        # 如果字典中有 result 字段，优先处理 result 字段的内容
        if 'result' in value:
            result_value = value['result']
            # 处理 result 字段（去除 knowledge_source 和指定字段）
            if isinstance(result_value, dict):
                # 复制字典，去除 knowledge_source、last_updated、created_at 和指定字段
                skip_keys = {'knowledge_source', 'name', '切片', 'last_updated', 'created_at'}
                cleaned_result = {k: v for k, v in result_value.items() if k not in skip_keys}
                return _format_value(cleaned_result)
            elif isinstance(result_value, list):
                # 如果是列表，递归处理每个元素（去除 knowledge_source）
                if len(result_value) == 0:
                    return "[]"
                # 如果是简单列表，直接连接
                if all(isinstance(x, (str, int, float)) for x in result_value[:3]):
                    return ", ".join(str(x) for x in result_value[:10]) + ("..." if len(result_value) > 10 else "")
                # 复杂列表，递归处理每个元素
                cleaned_items = []
                for item in result_value[:10]:
                    if isinstance(item, dict):
                        # 去除 knowledge_source、last_updated、created_at 和指定字段
                        skip_keys = {'knowledge_source', 'name', '切片', 'last_updated', 'created_at'}
                        cleaned_item = {k: v for k, v in item.items() if k not in skip_keys}
                        cleaned_items.append(_format_value(cleaned_item))
                    else:
                        cleaned_items.append(_format_value(item))
                result_str = "; ".join(cleaned_items)
                if len(result_value) > 10:
                    result_str += f" ...（共 {len(result_value)} 条）"
                return result_str
            else:
                # result 字段是其他类型，直接格式化
                return _format_value(result_value)
        else:
            # 没有 result 字段，正常处理字典，去除 knowledge_source、last_updated、created_at 和指定字段
            skip_keys = {'knowledge_source', 'name', '切片', 'last_updated', 'created_at'}
            cleaned_dict = {k: v for k, v in value.items() if k not in skip_keys}
            # 展平字典为 key: value 形式，避免嵌套 JSON
            parts = []
            for k, v in cleaned_dict.items():
                if not isinstance(v, (dict, list)) or len(str(v)) < 100:  # 避免大对象
                    parts.append(f"{k}: {_format_value(v)}")
            return "; ".join(parts) if parts else "{...}"

    # 如果是列表
    elif isinstance(value, list):
        if len(value) == 0:
            return "[]"
        # 如果是简单列表（如 [1,2,3] 或 ["a","b"]）
        if all(isinstance(x, (str, int, float)) for x in value[:3]):
            return ", ".join(str(x) for x in value[:10]) + ("..." if len(value) > 10 else "")
        else:
            # 复杂对象列表，检查是否有 {'result': {...}} 格式的元素
            # 检查前几个元素，判断是否都是 {'result': {...}} 格式
            has_result_format = False
            if value and isinstance(value[0], dict) and 'result' in value[0]:
                # 检查是否所有元素都是 {'result': {...}} 格式
                has_result_format = all(
                    isinstance(item, dict) and 'result' in item and isinstance(item.get('result'), dict)
                    for item in value[:3]  # 只检查前3个元素来判断模式
                )

            formatted_items = []
            for item in value[:10]:  # 最多处理10个元素
                if has_result_format and isinstance(item, dict) and 'result' in item:
                    # 如果是 {'result': {...}} 格式，分解 result 字段并去除 knowledge_source 和指定字段
                    result_value = item['result']
                    if isinstance(result_value, dict):
                        # 去除 knowledge_source、last_updated、created_at 和指定字段
                        skip_keys = {'knowledge_source', 'name', '切片', 'last_updated', 'created_at'}
                        cleaned_result = {k: v for k, v in result_value.items() if k not in skip_keys}
                        formatted_items.append(_format_value(cleaned_result))
                    else:
                        formatted_items.append(_format_value(result_value))
                elif isinstance(item, dict):
                    # 如果不是 {'result': {...}} 格式，去除字典中的 knowledge_source、last_updated、created_at 和指定字段
                    skip_keys = {'knowledge_source', 'name', '切片', 'last_updated', 'created_at'}
                    cleaned_item = {k: v for k, v in item.items() if k not in skip_keys}
                    formatted_items.append(_format_value(cleaned_item))
                else:
                    # 其他类型，正常处理
                    formatted_items.append(_format_value(item))

            result = "; ".join(formatted_items)
            if len(value) > 10:
                result += f" ...（共 {len(value)} 条）"
            return result
    else:
        return str(value)[:200]  # 截断超长内容


def serialize_graph_for_llm(graph_response: dict) -> str:
    """
    通用图谱结果序列化器，支持：
      - nodes（实体详情）
      - results（任意查询返回值：聚合/记录/字符串等）
    """
    data = graph_response.get("data", {})
    # 这里的过滤只影响 results 字段里的文本展示，不改动接口返回的 data.nodes/data.links。
    nodes = _filter_result_nodes_for_output(data.get("nodes", []))
    results = _filter_results_for_output(data.get("results", []))

    output_lines = []

    # ========== 1. 处理 results（核心查询返回值）==========
    if results:
        output_lines.append("【图谱查询直接结果】")
        if len(results) == 1 and not isinstance(results[0], (dict, list)):
            # 单值结果（如 count, max, 字符串）
            output_lines.append(f"  {results[0]}")
        else:
            # 多条记录或复杂结构
            for i, res in enumerate(results):  # 最多展示10条
                formatted = _format_value(res)
                output_lines.append(f"  [{i + 1}] {formatted}")

        output_lines.append("")

    # ========== 2. 处理 nodes（完整实体信息）==========
    if nodes:
        output_lines.append("【相关图谱实体详情】")
        for node in nodes:  # 防止过长，最多15个节点
            name = node.get("name", "Unnamed")
            labels = node.get("labels", [])
            props = node.get("properties", {})

            output_lines.append(f"  - 名称: {name}")
            if labels:
                output_lines.append(f"    标签: {', '.join(labels)}")

            # 过滤掉低价值字段（如 分数, 内部 id, name, 切片, last_updated, created_at）
            skip_props = {"分数", "id", "name", "切片", "last_updated", "created_at", "最后更新时间", "创建时间"}
            for k, v in props.items():
                if k not in skip_props and v not in (None, ""):
                    output_lines.append(f"    {k}: {v}")

            # 不再显示手册来源（knowledge_source），因为用户要求在 results 字段中不显示

            output_lines.append("")  # 节点间空行

    # ========== 返回结果 ==========
    if not output_lines:
        return "图谱查询完成，但未返回有效数据。"

    return "\n".join(output_lines).rstrip()

