package com.infoepoch.pms.agent.platform.chat.application;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.action.InterruptionMetadata;
import com.alibaba.cloud.ai.graph.action.InterruptionMetadata.ToolFeedback;
import com.alibaba.cloud.ai.graph.action.InterruptionMetadata.ToolFeedback.FeedbackResult;
import com.alibaba.cloud.ai.graph.checkpoint.Checkpoint;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infoepoch.pms.agent.platform.agent.domain.Agent;
import com.infoepoch.pms.agent.platform.chat.application.dto.PendingApprovalResponse;
import com.infoepoch.pms.agent.platform.chat.application.dto.PendingApprovalResponse.FieldItem;
import com.infoepoch.pms.agent.platform.chat.checkpoint.DatabaseCheckpointStore;
import com.infoepoch.pms.agent.platform.chatbot.domain.Chatbot;
import com.infoepoch.pms.agent.platform.shared.exception.BusinessException;
import com.infoepoch.pms.agent.platform.shared.response.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 审批状态服务。
 */
@Slf4j
@Service
public class ApprovalStateService {

    /**
     * JSON 解析与序列化工具。
     */
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    /**
     * 子图线程名前缀。
     */
    private static final String SUBGRAPH_THREAD_PREFIX = "subgraph_";

    private final ConcurrentMap<String, CapturedApproval> pendingApprovals = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, String> resolvedInterruptionIds = new ConcurrentHashMap<>();
    private final DatabaseCheckpointStore checkpointStore;

    public ApprovalStateService(@Autowired(required = false) DatabaseCheckpointStore checkpointStore) {
        this.checkpointStore = checkpointStore;
    }

    /**
     * 查询会话当前待审批事项。
     *
     * @param sessionId 会话 ID
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse getPendingApproval(String sessionId) {
        return getPendingApproval(sessionId, sessionId);
    }

    /**
     * 查询会话当前活跃上下文线程上的待审批事项。
     *
     * @param sessionId       会话 ID
     * @param activeThreadId  当前活跃上下文线程 ID
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse getPendingApproval(String sessionId, String activeThreadId) {
        return resolvePendingApproval(sessionId, activeThreadId, Collections.emptySet())
                .map(CapturedApproval::pendingApproval)
                .orElse(PendingApprovalResponse.none());
    }

    /**
     * 查询会话当前待审批事项，并通过 chatbot 的子智能体列表解析真实 agentId。
     *
     * @param sessionId 会话 ID
     * @param chatbot   当前会话关联的 chatbot
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse getPendingApproval(String sessionId, Chatbot chatbot) {
        return getPendingApproval(sessionId, sessionId, chatbot);
    }

    /**
     * 查询会话当前活跃上下文线程上的待审批事项，并通过 chatbot 的子智能体列表解析真实 agentId。
     *
     * @param sessionId       会话 ID
     * @param activeThreadId  当前活跃上下文线程 ID
     * @param chatbot         当前会话关联的 chatbot
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse getPendingApproval(String sessionId, String activeThreadId, Chatbot chatbot) {
        Set<Agent> subAgents = chatbot == null ? Collections.emptySet() : chatbot.getSubAgents();
        return resolvePendingApproval(sessionId, activeThreadId, subAgents)
                .map(CapturedApproval::pendingApproval)
                .orElse(PendingApprovalResponse.none());
    }

    /**
     * 从 checkpoint 中捕获当前待审批事项。
     *
     * @param requestKey 流式请求标识
     * @param sessionId  会话 ID
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse capturePendingApproval(String requestKey, String sessionId) {
        return capturePendingApproval(requestKey, sessionId, sessionId.toString(), null, Collections.emptySet());
    }

    /**
     * 从中断输出中捕获当前待审批事项。
     *
     * @param requestKey           流式请求标识
     * @param sessionId            会话 ID
     * @param interruptionMetadata 中断元数据
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse capturePendingApproval(String requestKey,
                                                          String sessionId,
                                                          InterruptionMetadata interruptionMetadata) {
        return capturePendingApproval(requestKey, sessionId, sessionId.toString(), interruptionMetadata, Collections.emptySet());
    }

    /**
     * 从中断输出中捕获当前待审批事项，并通过 chatbot 的子智能体列表解析 agentId。
     *
     * @param requestKey           流式请求标识
     * @param sessionId            会话 ID
     * @param interruptionMetadata 中断元数据
     * @param chatbot              当前会话关联的 chatbot，用于 agentName → agentId 映射
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse capturePendingApproval(String requestKey,
                                                          String sessionId,
                                                          InterruptionMetadata interruptionMetadata,
                                                          Chatbot chatbot) {
        return capturePendingApproval(requestKey, sessionId, sessionId.toString(), interruptionMetadata, chatbot);
    }

    /**
     * 从中断输出中捕获当前待审批事项，并通过 chatbot 的子智能体列表解析 agentId。
     *
     * @param requestKey          流式请求标识
     * @param sessionId           会话 ID
     * @param activeThreadId      当前活跃上下文线程 ID
     * @param interruptionMetadata 中断元数据
     * @param chatbot             当前会话关联的 chatbot，用于 agentName → agentId 映射
     * @return 待审批响应；不存在时返回空响应
     */
    public PendingApprovalResponse capturePendingApproval(String requestKey,
                                                          String sessionId,
                                                          String activeThreadId,
                                                          InterruptionMetadata interruptionMetadata,
                                                          Chatbot chatbot) {
        Set<Agent> subAgents = (chatbot != null) ? chatbot.getSubAgents() : Collections.emptySet();
        return capturePendingApproval(requestKey, sessionId, activeThreadId, interruptionMetadata, subAgents);
    }

    /**
     * 内部实现：从中断输出捕获待审批事项，支持通过子智能体列表解析 agentId。
     */
    private PendingApprovalResponse capturePendingApproval(String requestKey,
                                                           String sessionId,
                                                           String activeThreadId,
                                                           InterruptionMetadata interruptionMetadata,
                                                           Set<Agent> subAgents) {
        if (interruptionMetadata != null) {
            CapturedApproval cachedApproval = pendingApprovals.get(sessionId);
            if (isUsablePending(sessionId, activeThreadId, cachedApproval)) {
                return cachedApproval.pendingApproval();
            }

            String checkpointThreadId = resolveCheckpointThreadId(activeThreadId, interruptionMetadata);
            if (!StringUtils.hasText(checkpointThreadId)) {
                return PendingApprovalResponse.none();
            }
            String interruptionId = loadCurrentInterruptionId(checkpointThreadId).orElse(null);
            if (!StringUtils.hasText(interruptionId)) {
                return PendingApprovalResponse.none();
            }

            CapturedApproval capturedApproval = buildCapturedApproval(sessionId, checkpointThreadId,
                    interruptionId, interruptionMetadata, subAgents)
                    .orElse(null);
            if (capturedApproval == null || !capturedApproval.pendingApproval().isPending()) {
                pendingApprovals.remove(sessionId);
                return PendingApprovalResponse.none();
            }

            String resolvedInterruptionId = resolvedInterruptionIds.get(sessionId);
            if (Objects.equals(resolvedInterruptionId, capturedApproval.interruptionId())) {
                return PendingApprovalResponse.none();
            }

            pendingApprovals.put(sessionId, capturedApproval);
            resolvedInterruptionIds.remove(sessionId);
            log.debug("捕获待审批事项成功, sessionId={}, requestKey={}, interruptionId={}",
                    sessionId, requestKey, capturedApproval.interruptionId());
            return capturedApproval.pendingApproval();
        }

        CapturedApproval capturedApproval = resolvePendingApproval(sessionId, activeThreadId, subAgents).orElse(null);
        if (capturedApproval == null) {
            return PendingApprovalResponse.none();
        }
        pendingApprovals.put(sessionId, capturedApproval);
        return capturedApproval.pendingApproval();
    }

    /**
     * 使会话当前待审批事项失效。
     *
     * @param sessionId 会话 ID
     */
    public void invalidatePendingApproval(String sessionId) {
        CapturedApproval removed = pendingApprovals.remove(sessionId);
        if (removed != null) {
            resolvedInterruptionIds.put(sessionId, removed.interruptionId());
            return;
        }

        resolvePendingApproval(sessionId, sessionId.toString(), Collections.emptySet()).ifPresent(capturedApproval ->
                resolvedInterruptionIds.put(sessionId, capturedApproval.interruptionId()));
        pendingApprovals.remove(sessionId);
    }

    /**
     * 清理会话相关的审批缓存与 checkpoint 线程。
     *
     * @param sessionId 会话 ID
     */
    public void clearSessionState(String sessionId) {
        clearSessionState(sessionId, List.of(sessionId.toString()));
    }

    /**
     * 清理会话相关的审批缓存与 checkpoint 根线程。
     *
     * @param sessionId      会话 ID
     * @param rootThreadIds  该会话涉及的根线程 ID 列表
     */
    public void clearSessionState(String sessionId, List<String> rootThreadIds) {
        pendingApprovals.remove(sessionId);
        resolvedInterruptionIds.remove(sessionId);
        deleteCheckpointThreads(rootThreadIds);
    }

    /**
     * 准备审批决策对应的恢复上下文。
     *
     * @param sessionId      会话 ID
     * @param interruptionId 中断 ID
     * @param decision       审批决策
     * @param editedFields   用户手动编辑的字段值；为 null 或空时跳过注入
     * @return 恢复上下文
     */
    public ApprovalDecisionContext streamApprovalDecision(String sessionId,
                                                          String interruptionId,
                                                          String decision,
                                                          Map<String, Object> editedFields) {
        return streamApprovalDecision(sessionId, sessionId.toString(), interruptionId, decision, editedFields);
    }

    /**
     * 准备审批决策对应的恢复上下文，并限制在当前活跃上下文线程边界内。
     *
     * @param sessionId       会话 ID
     * @param activeThreadId  当前活跃上下文线程 ID
     * @param interruptionId  中断 ID
     * @param decision        审批决策
     * @param editedFields    用户手动编辑的字段值；为 null 或空时跳过注入
     * @return 恢复上下文
     */
    public ApprovalDecisionContext streamApprovalDecision(String sessionId,
                                                          String activeThreadId,
                                                          String interruptionId,
                                                          String decision,
                                                          Map<String, Object> editedFields) {
        if (!StringUtils.hasText(interruptionId)) {
            throw new BusinessException(ResultCode.PARAM_ERROR, "interruptionId 不能为空");
        }
        String normalizedDecision = normalizeDecision(decision);
        if (!"approve".equals(normalizedDecision) && !"reject".equals(normalizedDecision)) {
            throw new BusinessException(ResultCode.PARAM_ERROR, "decision 仅支持 approve 或 reject");
        }

        String resolvedInterruptionId = resolvedInterruptionIds.get(sessionId);
        if (Objects.equals(resolvedInterruptionId, interruptionId)) {
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "审批已失效，请基于最新申请重新确认");
        }

        CapturedApproval capturedApproval = pendingApprovals.get(sessionId);
        if (!isUsablePending(sessionId, activeThreadId, capturedApproval)) {
            capturedApproval = resolvePendingApproval(sessionId, activeThreadId, Collections.emptySet()).orElse(null);
        }

        if (capturedApproval == null || !capturedApproval.pendingApproval().isPending()) {
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "当前没有可处理的待审批事项");
        }
        if (!Objects.equals(capturedApproval.interruptionId(), interruptionId)) {
            throw new BusinessException(ResultCode.SYSTEM_ERROR, "审批已失效，请基于最新申请重新确认");
        }

        pendingApprovals.remove(sessionId);
        resolvedInterruptionIds.put(sessionId, interruptionId);
        InterruptionMetadata sourceMetadata = injectEditedFields(capturedApproval.interruptionMetadata(), editedFields);
        InterruptionMetadata humanFeedback = buildHumanFeedback(sourceMetadata, normalizedDecision);
        return new ApprovalDecisionContext(
                capturedApproval.pendingApproval(),
                capturedApproval.checkpointThreadId(),
                capturedApproval.interruptionId(),
                humanFeedback
        );
    }

    /**
     * 解析会话当前可用的待审批快照。
     * 优先使用内存缓存，缓存不可用时回退到 checkpoint 恢复。
     *
     * @param sessionId 会话 ID
     * @param subAgents 当前 chatbot 子智能体列表（用于补全 agentId）
     * @return 待审批快照，不存在时返回空
     */
    private Optional<CapturedApproval> resolvePendingApproval(String sessionId, String activeThreadId, Set<Agent> subAgents) {
        CapturedApproval cached = pendingApprovals.get(sessionId);
        if (isUsablePending(sessionId, activeThreadId, cached)
                && (subAgents.isEmpty() || hasResolvedAgent(cached.pendingApproval()))) {
            return Optional.of(cached);
        }

        if (checkpointStore == null) {
            return Optional.ofNullable(cached).filter(approval -> isUsablePending(sessionId, activeThreadId, approval));
        }

        try {
            for (String threadId : findCandidateThreadIds(activeThreadId)) {
                RunnableConfig config = RunnableConfig.builder()
                        .threadId(threadId)
                        .build();
                Optional<CapturedApproval> capturedApproval = checkpointStore.get(config)
                        .flatMap(checkpoint -> buildCapturedApproval(sessionId, threadId, checkpoint, subAgents))
                        .filter(approval -> belongsToActiveThread(activeThreadId, approval.checkpointThreadId()))
                        .filter(approval -> !Objects.equals(resolvedInterruptionIds.get(sessionId),
                                approval.interruptionId()));
                if (capturedApproval.isPresent()) {
                    pendingApprovals.put(sessionId, capturedApproval.get());
                    return capturedApproval;
                }
            }
            return Optional.ofNullable(cached).filter(approval -> isUsablePending(sessionId, activeThreadId, approval));
        } catch (Exception ex) {
            log.debug("读取待审批事项失败, sessionId={}", sessionId, ex);
            return Optional.ofNullable(cached).filter(approval -> isUsablePending(sessionId, activeThreadId, approval));
        }
    }

    /**
     * 根据 checkpoint 载荷构建待审批快照。
     *
     * @param sessionId          会话 ID
     * @param checkpointThreadId checkpoint 线程 ID
     * @param checkpoint         checkpoint 快照
     * @param subAgents          当前 chatbot 子智能体列表
     * @return 构建出的待审批快照，不可构建时返回空
     */
    private Optional<CapturedApproval> buildCapturedApproval(String sessionId,
                                                             String checkpointThreadId,
                                                             Checkpoint checkpoint,
                                                             Set<Agent> subAgents) {
        Object interruptionValue = checkpoint.getState().get(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY);
        if (interruptionValue instanceof InterruptionMetadata interruptionMetadata) {
            return buildCapturedApproval(sessionId, checkpointThreadId, checkpoint.getId(), interruptionMetadata, subAgents);
        }
        if (interruptionValue instanceof Map<?, ?> map) {
            Optional<InterruptionMetadata> reconstructed = reconstructInterruptionMetadata(map);
            if (reconstructed.isPresent()) {
                return buildCapturedApproval(sessionId, checkpointThreadId, checkpoint.getId(), reconstructed.get(), subAgents);
            }
        }
        return Optional.empty();
    }

    /**
     * 基于中断元数据组装待审批响应。
     *
     * @param sessionId            会话 ID
     * @param checkpointThreadId   checkpoint 线程 ID
     * @param interruptionId       中断 ID
     * @param interruptionMetadata 中断元数据
     * @param subAgents            当前 chatbot 子智能体列表
     * @return 构建出的待审批快照，不可构建时返回空
     */
    private Optional<CapturedApproval> buildCapturedApproval(String sessionId,
                                                             String checkpointThreadId,
                                                             String interruptionId,
                                                             InterruptionMetadata interruptionMetadata,
                                                             Set<Agent> subAgents) {
        List<FieldItem> allFields = extractFieldItems(interruptionMetadata);
        List<FieldItem> summaryFields = allFields.stream()
                .filter(this::hasNonEmptyValue)
                .limit(4)
                .toList();
        if (allFields.isEmpty()) {
            return Optional.empty();
        }

        String agentName = resolveBusinessAgentName(interruptionMetadata, checkpointThreadId);
        String agentId = resolveAgentId(agentName, subAgents);
        PendingApprovalResponse pendingApproval = PendingApprovalResponse.pending(
                interruptionId,
                sessionId,
                agentId,
                agentName,
                summaryFields,
                allFields
        );
        return Optional.of(new CapturedApproval(interruptionId, checkpointThreadId, interruptionMetadata, pendingApproval));
    }

    /**
     * 通过智能体名称从子智能体列表中查找数据库 String。
     * 名称匹配规则与 DynamicAgentBuilder.sanitizeName 一致：替换非法字符为下划线。
     *
     * @param agentName 中断元数据中的智能体名称（已 sanitized）
     * @param subAgents chatbot 关联的子智能体列表
     * @return 匹配到的智能体 String 字符串；未找到时返回 null
     */
    private String resolveAgentId(String agentName, Set<Agent> subAgents) {
        if (!StringUtils.hasText(agentName) || subAgents.isEmpty()) {
            return null;
        }
        for (Agent agent : subAgents) {
            String sanitized = agent.getName().replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "_");
            if (sanitized.equals(agentName)) {
                return agent.getId().toString();
            }
        }
        return null;
    }

    /**
     * 判断待审批响应中的智能体信息是否已解析为业务智能体。
     *
     * @param pendingApproval 待审批响应
     * @return true 表示 agentId/agentName 均有效且非 Hook 节点
     */
    private boolean hasResolvedAgent(PendingApprovalResponse pendingApproval) {
        return StringUtils.hasText(pendingApproval.getAgentId())
                && StringUtils.hasText(pendingApproval.getAgentName())
                && !isHookNodeName(pendingApproval.getAgentName());
    }

    /**
     * 解析业务智能体名称。
     * 优先使用中断元数据中的 agent，其次从线程 ID 中提取，最后回退到节点名。
     *
     * @param interruptionMetadata 中断元数据
     * @param checkpointThreadId   checkpoint 线程 ID
     * @return 业务智能体名称
     */
    private String resolveBusinessAgentName(InterruptionMetadata interruptionMetadata, String checkpointThreadId) {
        String metadataAgentName = stringValue(interruptionMetadata.agent());
        if (StringUtils.hasText(metadataAgentName) && !isHookNodeName(metadataAgentName)) {
            return metadataAgentName;
        }
        String threadAgentName = extractAgentNameFromThreadId(checkpointThreadId);
        if (StringUtils.hasText(threadAgentName)) {
            return threadAgentName;
        }
        return StringUtils.hasText(metadataAgentName) ? metadataAgentName : interruptionMetadata.node();
    }

    /**
     * 判断名称是否为框架 Hook 节点名。
     *
     * @param value 待判断名称
     * @return true 表示 Hook 节点名
     */
    private boolean isHookNodeName(String value) {
        return StringUtils.hasText(value) && value.startsWith("_AGENT_HOOK_");
    }

    /**
     * 从 checkpoint 线程 ID 中提取子智能体名称。
     *
     * @param checkpointThreadId checkpoint 线程 ID
     * @return 提取出的智能体名称，无法提取时返回 null
     */
    private String extractAgentNameFromThreadId(String checkpointThreadId) {
        if (!StringUtils.hasText(checkpointThreadId)) {
            return null;
        }
        String marker = "_" + SUBGRAPH_THREAD_PREFIX;
        int markerIndex = checkpointThreadId.indexOf(marker);
        if (markerIndex < 0) {
            return null;
        }
        String extracted = checkpointThreadId.substring(markerIndex + marker.length());
        return StringUtils.hasText(extracted) ? extracted : null;
    }

    /**
     * 从 Map 结构重建 InterruptionMetadata。
     *
     * @param rawMetadata 原始中断元数据
     * @return 重建结果，不可重建时返回空
     */
    private Optional<InterruptionMetadata> reconstructInterruptionMetadata(Map<?, ?> rawMetadata) {
        Object nodeId = rawMetadata.get("nodeId");
        Object state = rawMetadata.get("state");
        if (!(state instanceof com.alibaba.cloud.ai.graph.OverAllState overAllState)) {
            return Optional.empty();
        }

        InterruptionMetadata.Builder builder = InterruptionMetadata.builder(String.valueOf(nodeId), overAllState);
        Object toolFeedbacks = rawMetadata.get("toolFeedbacks");
        if (toolFeedbacks instanceof Collection<?> collection) {
            for (Object item : collection) {
                if (item instanceof Map<?, ?> feedbackMap) {
                    toToolFeedback(feedbackMap).ifPresent(builder::addToolFeedback);
                }
            }
        }
        return Optional.of(builder.build());
    }

    /**
     * 将原始工具反馈 Map 转为 ToolFeedback。
     *
     * @param rawFeedback 原始工具反馈
     * @return 转换后的 ToolFeedback，关键字段缺失时返回空
     */
    private Optional<ToolFeedback> toToolFeedback(Map<?, ?> rawFeedback) {
        String id = stringValue(rawFeedback.get("id"));
        String name = stringValue(rawFeedback.get("name"));
        String arguments = stringValue(rawFeedback.get("arguments"));
        String description = stringValue(rawFeedback.get("description"));
        Object result = rawFeedback.get("result");
        if (!StringUtils.hasText(id) || !StringUtils.hasText(name) || !StringUtils.hasText(arguments)) {
            return Optional.empty();
        }
        FeedbackResult feedbackResult = null;
        if (result != null) {
            try {
                feedbackResult = FeedbackResult.valueOf(String.valueOf(result).trim().toUpperCase(Locale.ROOT));
            } catch (Exception ignored) {
                feedbackResult = null;
            }
        }
        return Optional.of(ToolFeedback.builder()
                .id(id)
                .name(name)
                .arguments(arguments)
                .description(description)
                .result(feedbackResult)
                .build());
    }

    /**
     * 读取指定线程当前 checkpoint 的中断 ID。
     *
     * @param threadId 线程 ID
     * @return 中断 ID，不存在或读取失败时返回空
     */
    private Optional<String> loadCurrentInterruptionId(String threadId) {
        if (checkpointStore == null) {
            return Optional.empty();
        }
        try {
            RunnableConfig config = RunnableConfig.builder()
                    .threadId(threadId)
                    .build();
            return checkpointStore.get(config).map(Checkpoint::getId);
        } catch (Exception ex) {
            log.debug("读取当前中断 ID 失败, threadId={}", threadId, ex);
            return Optional.empty();
        }
    }

    /**
     * 查询会话可用于恢复审批的候选线程 ID。
     *
     * @return 候选线程 ID 列表（按最近保存时间倒序）
     */
    private List<String> findCandidateThreadIds(String activeThreadId) {
        if (!StringUtils.hasText(activeThreadId) || checkpointStore == null) {
            return List.of();
        }
        try {
            return checkpointStore.findCandidateThreadIds(activeThreadId);
        } catch (Exception ex) {
            log.debug("查询候选审批线程失败, activeThreadId={}", activeThreadId, ex);
            return List.of(activeThreadId);
        }
    }

    /**
     * 删除会话对应的根线程与子图线程，依赖 GraphCheckpoint 的级联删除清理历史快照。
     *
     */
    private void deleteCheckpointThreads(List<String> rootThreadIds) {
        if (checkpointStore == null || rootThreadIds == null || rootThreadIds.isEmpty()) {
            return;
        }
        try {
            checkpointStore.deleteThreadGroups(rootThreadIds);
        } catch (Exception ex) {
            log.warn("删除会话 checkpoint 线程失败, rootThreadIds={}", rootThreadIds, ex);
        }
    }

    /**
     * 根据中断元数据推导 checkpoint 线程 ID。
     *
     * @param interruptionMetadata 中断元数据
     * @return checkpoint 线程 ID
     */
    private String resolveCheckpointThreadId(String activeThreadId, InterruptionMetadata interruptionMetadata) {
        String rawAgentId = stringValue(interruptionMetadata.state()
                .value(RunnableConfig.AGENT_NAME_KEY)
                .orElse(null));
        if (!StringUtils.hasText(activeThreadId)) {
            return null;
        }
        if (!StringUtils.hasText(rawAgentId) || Objects.equals(rawAgentId, activeThreadId)) {
            return activeThreadId;
        }
        if (rawAgentId.startsWith(SUBGRAPH_THREAD_PREFIX)) {
            return activeThreadId + "_" + rawAgentId;
        }
        return activeThreadId;
    }

    /**
     * 判断缓存中的待审批快照是否可用。
     *
     * @param sessionId        会话 ID
     * @param capturedApproval 待审批快照
     * @return true 表示快照仍可处理
     */
    private boolean isUsablePending(String sessionId, String activeThreadId, CapturedApproval capturedApproval) {
        return capturedApproval != null
                && capturedApproval.pendingApproval().isPending()
                && belongsToActiveThread(activeThreadId, capturedApproval.checkpointThreadId())
                && !Objects.equals(resolvedInterruptionIds.get(sessionId), capturedApproval.interruptionId());
    }

    /**
     * 判断捕获到的审批快照是否仍属于当前活跃上下文线程。
     *
     * @param activeThreadId     当前活跃上下文线程 ID
     * @param checkpointThreadId 审批快照所在 checkpoint 线程 ID
     * @return true 表示仍属于当前上下文边界
     */
    private boolean belongsToActiveThread(String activeThreadId, String checkpointThreadId) {
        if (!StringUtils.hasText(activeThreadId) || !StringUtils.hasText(checkpointThreadId)) {
            return false;
        }
        return Objects.equals(activeThreadId, checkpointThreadId)
                || checkpointThreadId.startsWith(activeThreadId + "_" + SUBGRAPH_THREAD_PREFIX);
    }

    /**
     * 从中断元数据中提取字段项。
     *
     * @param interruptionMetadata 中断元数据
     * @return 字段项列表
     */
    private List<FieldItem> extractFieldItems(InterruptionMetadata interruptionMetadata) {
        List<FieldItem> items = new ArrayList<>();
        for (ToolFeedback toolFeedback : interruptionMetadata.toolFeedbacks()) {
            items.addAll(extractFieldItems(toolFeedback.getArguments()));
        }
        return List.copyOf(items);
    }

    /**
     * 从任意结构值中递归提取字段项。
     *
     * @param value 待提取值
     * @return 字段项列表
     */
    private List<FieldItem> extractFieldItems(Object value) {
        if (value == null) {
            return List.of();
        }
        if (value instanceof InterruptionMetadata interruptionMetadata) {
            return extractFieldItems(interruptionMetadata);
        }
        if (value instanceof CharSequence sequence && StringUtils.hasText(sequence)) {
            String text = sequence.toString().trim();
            if (looksLikeJson(text)) {
                try {
                    JsonNode jsonNode = OBJECT_MAPPER.readTree(text);
                    return extractFieldItems(jsonNode);
                } catch (Exception ignored) {
                    // 回退到纯文本字段。
                }
            }
            return List.of(new FieldItem("feedback", "审批反馈", text));
        }
        if (value instanceof Collection<?> collection) {
            List<FieldItem> items = new ArrayList<>();
            for (Object item : collection) {
                toFieldItem(item).ifPresent(items::add);
            }
            return List.copyOf(items);
        }
        if (value instanceof Map<?, ?> map) {
            List<FieldItem> items = new ArrayList<>();
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                String key = stringValue(entry.getKey());
                Object entryValue = entry.getValue();
                if (entryValue instanceof Collection<?> nestedCollection) {
                    List<FieldItem> nestedItems = extractFieldItems(nestedCollection);
                    if (!nestedItems.isEmpty()) {
                        items.addAll(nestedItems);
                        continue;
                    }
                }
                if (entryValue instanceof Map<?, ?> nestedMap) {
                    List<FieldItem> nestedItems = extractFieldItems(nestedMap);
                    if (!nestedItems.isEmpty()) {
                        items.addAll(nestedItems);
                        continue;
                    }
                }
                String label = StringUtils.hasText(key) ? key : "审批反馈";
                String itemValue = stringifyValue(entryValue);
                if (StringUtils.hasText(itemValue)) {
                    items.add(new FieldItem(StringUtils.hasText(key) ? key : "feedback", label, itemValue));
                }
            }
            return List.copyOf(items);
        }
        return toFieldItem(value).map(List::of).orElse(List.of());
    }

    /**
     * 将单个对象转换为字段项。
     *
     * @param value 原始对象
     * @return 字段项，不可转换时返回空
     */
    private Optional<FieldItem> toFieldItem(Object value) {
        if (value instanceof Map<?, ?> map) {
            String key = stringValue(firstPresent(map, "key", "name", "field"));
            String label = stringValue(firstPresent(map, "label", "title", "displayName"));
            String itemValue = stringifyValue(firstPresent(map, "value", "content", "text"));
            if (!StringUtils.hasText(key) && !StringUtils.hasText(label) && !StringUtils.hasText(itemValue)) {
                return Optional.empty();
            }
            return Optional.of(new FieldItem(
                    StringUtils.hasText(key) ? key : "feedback",
                    StringUtils.hasText(label) ? label : "审批反馈",
                    StringUtils.hasText(itemValue) ? itemValue : String.valueOf(value)
            ));
        }
        if (value != null) {
            return Optional.of(new FieldItem("feedback", "审批反馈", String.valueOf(value)));
        }
        return Optional.empty();
    }

    /**
     * 从 JSON 节点递归提取字段项。
     *
     * @param jsonNode JSON 节点
     * @return 字段项列表
     */
    private List<FieldItem> extractFieldItems(JsonNode jsonNode) {
        if (jsonNode == null || jsonNode.isNull()) {
            return List.of();
        }
        if (jsonNode.isObject()) {
            List<FieldItem> items = new ArrayList<>();
            jsonNode.fields().forEachRemaining(entry -> {
                JsonNode entryValue = entry.getValue();
                if (entryValue != null && entryValue.isObject()) {
                    List<FieldItem> nested = extractFieldItems(entryValue);
                    if (!nested.isEmpty()) {
                        items.addAll(nested);
                        return;
                    }
                }
                if (entryValue != null && entryValue.isArray()) {
                    List<FieldItem> nested = extractFieldItems(entryValue);
                    if (!nested.isEmpty()) {
                        items.addAll(nested);
                        return;
                    }
                }
                items.add(new FieldItem(entry.getKey(), entry.getKey(), stringifyJsonNode(entryValue)));
            });
            return List.copyOf(items);
        }
        if (jsonNode.isArray()) {
            List<FieldItem> items = new ArrayList<>();
            for (int i = 0; i < jsonNode.size(); i++) {
                JsonNode element = jsonNode.get(i);
                if (element != null && element.isObject()) {
                    items.addAll(extractFieldItems(element));
                } else if (element != null && element.isArray()) {
                    items.addAll(extractFieldItems(element));
                } else {
                    items.add(new FieldItem("item" + i, "item" + i, stringifyJsonNode(element)));
                }
            }
            return List.copyOf(items);
        }
        return List.of(new FieldItem("feedback", "审批反馈", stringifyJsonNode(jsonNode)));
    }

    /**
     * 返回 Map 中第一个命中的候选 key 对应值。
     *
     * @param map  数据映射
     * @param keys 候选 key 列表
     * @return 命中的值，不存在时返回 null
     */
    private Object firstPresent(Map<?, ?> map, String... keys) {
        for (String key : keys) {
            Object candidate = map.get(key);
            if (candidate != null) {
                return candidate;
            }
        }
        return null;
    }

    /**
     * 将对象值标准化为字符串表示。
     *
     * @param value 原始值
     * @return 字符串结果，value 为 null 时返回 null
     */
    private String stringifyValue(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof CharSequence sequence) {
            String text = sequence.toString().trim();
            if (StringUtils.hasText(text) && looksLikeJson(text)) {
                try {
                    return stringifyJsonNode(OBJECT_MAPPER.readTree(text));
                } catch (Exception ignored) {
                    return text;
                }
            }
            return text;
        }
        if (value instanceof JsonNode jsonNode) {
            return stringifyJsonNode(jsonNode);
        }
        return String.valueOf(value);
    }

    /**
     * 将 JSON 节点转换为可展示字符串。
     *
     * @param jsonNode JSON 节点
     * @return 字符串结果
     */
    private String stringifyJsonNode(JsonNode jsonNode) {
        if (jsonNode == null || jsonNode.isNull()) {
            return "";
        }
        if (jsonNode.isValueNode()) {
            return jsonNode.asText();
        }
        return jsonNode.toString();
    }

    /**
     * 粗略判断文本是否为 JSON 字符串。
     *
     * @param text 文本
     * @return true 表示可能是 JSON
     */
    private boolean looksLikeJson(String text) {
        return text.startsWith("{") || text.startsWith("[");
    }

    /**
     * 将对象转换为去首尾空白的字符串。
     *
     * @param value 原始对象
     * @return 转换后的字符串，value 为 null 时返回 null
     */
    private String stringValue(Object value) {
        return value == null ? null : String.valueOf(value).trim();
    }

    /**
     * 规范化审批决策文本。
     *
     * @param decision 原始决策
     * @return 小写且去空白的决策文本
     */
    private String normalizeDecision(String decision) {
        return decision == null ? "" : decision.trim().toLowerCase(Locale.ROOT);
    }

    /**
     * 判断字段项值是否非空。
     *
     * @param fieldItem 字段项
     * @return true 表示 value 非空白
     */
    private boolean hasNonEmptyValue(FieldItem fieldItem) {
        return fieldItem != null && StringUtils.hasText(fieldItem.getValue());
    }

    /**
     * 将用户编辑的字段值注入 InterruptionMetadata 的 tool arguments。
     * editedFields 为 null 或空时原样返回，不做任何修改。
     * 只覆盖 editedFields 中存在的 key，不增加新 key，不删除原有 key。
     */
    private InterruptionMetadata injectEditedFields(InterruptionMetadata original,
                                                    Map<String, Object> editedFields) {
        if (editedFields == null || editedFields.isEmpty()) {
            return original;
        }

        InterruptionMetadata.Builder builder = InterruptionMetadata.builder(original.node(), original.state());
        if (original.getToolsAutomaticallyApproved() != null) {
            builder.toolsAutomaticallyApproved(original.getToolsAutomaticallyApproved());
        }
        for (ToolFeedback toolFeedback : original.toolFeedbacks()) {
            String injectedArguments = injectIntoArguments(toolFeedback.getArguments(), editedFields);
            builder.addToolFeedback(ToolFeedback.builder(toolFeedback)
                    .arguments(injectedArguments)
                    .build());
        }
        return builder.build();
    }

    /**
     * 将 editedFields 中的 key 覆盖写入 JSON 格式的 arguments 字符串。
     * 若原始 arguments 无法解析为 JSON 对象，则原样返回。
     * 值为 null 的条目按空字符串写入。
     */
    private String injectIntoArguments(String arguments, Map<String, Object> editedFields) {
        if (!StringUtils.hasText(arguments) || !looksLikeJson(arguments.trim())) {
            return arguments;
        }
        try {
            @SuppressWarnings("unchecked")
            Map<String, Object> argsMap = OBJECT_MAPPER.readValue(arguments,
                    OBJECT_MAPPER.getTypeFactory().constructMapType(LinkedHashMap.class, String.class, Object.class));
            for (Map.Entry<String, Object> entry : editedFields.entrySet()) {
                if (argsMap.containsKey(entry.getKey())) {
                    argsMap.put(entry.getKey(), entry.getValue() != null ? entry.getValue() : "");
                }
            }
            return OBJECT_MAPPER.writeValueAsString(argsMap);
        } catch (Exception ex) {
            log.warn("注入 editedFields 失败，返回原始 arguments, arguments={}", arguments, ex);
            return arguments;
        }
    }

    /**
     * 基于审批决策构建用于 resume 的人类反馈元数据。
     *
     * @param original           原始中断元数据
     * @param normalizedDecision 规范化后的审批决策
     * @return 人类反馈元数据
     */
    private InterruptionMetadata buildHumanFeedback(InterruptionMetadata original, String normalizedDecision) {
        FeedbackResult result = "approve".equals(normalizedDecision)
                ? FeedbackResult.APPROVED
                : FeedbackResult.REJECTED;

        InterruptionMetadata.Builder builder = InterruptionMetadata.builder(original.node(), original.state());
        if (original.getToolsAutomaticallyApproved() != null) {
            builder.toolsAutomaticallyApproved(original.getToolsAutomaticallyApproved());
        }
        for (ToolFeedback toolFeedback : original.toolFeedbacks()) {
            builder.addToolFeedback(ToolFeedback.builder(toolFeedback)
                    .result(result)
                    .build());
        }
        return builder.build();
    }

    /**
     * 审批决策恢复上下文。
     *
     * @param pendingApproval 待审批响应
     * @param humanFeedback   人类反馈元数据
     */
    public record ApprovalDecisionContext(PendingApprovalResponse pendingApproval,
                                          String checkpointThreadId,
                                          String checkpointId,
                                          InterruptionMetadata humanFeedback) {
    }

    private record CapturedApproval(String interruptionId,
                                    String checkpointThreadId,
                                    InterruptionMetadata interruptionMetadata,
                                    PendingApprovalResponse pendingApproval) {
    }
}
