package com.agent.management.engine;

import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 根据 DAG 与 WorkflowContext 构建节点工作区。
 *
 * <p>变量注入策略：严格作用域隔离 ——
 * <ul>
 *   <li>初始输入以扁平形式注入（运行级 key，不涉及节点冲突）</li>
 *   <li>每个可达前驱的输出以 {predecessorId}__{varName} 形式注入，互不覆盖</li>
 *   <li>不再注入无前缀的扁平形式，避免同名输出互相覆盖</li>
 * </ul>
 * 后继节点通过 {@link NodeInputResolver} 的显式 mapping 或四级隐式回退取值。
 */
@Component
public class NodeWorkspaceBuilder {

    /** 节点 ID 与变量名之间的分隔符，避免 nodeId 含下划线时产生歧义 */
    public static final String NODE_FIELD_SEPARATOR = "__";

    private final AgentIgnoreFilter agentIgnoreFilter;

    public NodeWorkspaceBuilder(AgentIgnoreFilter agentIgnoreFilter) {
        this.agentIgnoreFilter = agentIgnoreFilter;
    }

    /**
     * 为指定节点构建工作区（静态可达前驱）。
     */
    public NodeWorkspace build(String nodeId, DagResolver.ResolvedDag dag, WorkflowContext context) {
        return build(nodeId, dag, context, Collections.emptySet());
    }

    /**
     * 为指定节点构建工作区（支持运行时活跃边过滤）。
     *
     * @param nodeId      当前节点 ID
     * @param dag         已解析的 DAG
     * @param context     工作流上下文
     * @param activeEdges 活跃边 key 集合（source-sourceHandle-target）；空集合表示使用静态可达集
     */
    public NodeWorkspace build(String nodeId, DagResolver.ResolvedDag dag,
                                WorkflowContext context, Set<String> activeEdges) {
        NodeWorkspace workspace = new NodeWorkspace();
        workspace.setRunId(context.getRunId());

        // 1. 初始输入始终可用（扁平形式）
        workspace.getVariables().putAll(context.getInitialInputs());

        // 2. 收集可达前驱节点，按拓扑顺序注入命名空间变量
        Set<String> reachable = WorkflowScopeResolver.getReachablePredecessors(
                nodeId, dag, activeEdges == null ? Collections.emptySet() : activeEdges);
        List<String> ordered = orderByLevels(reachable, dag);

        for (String predecessorId : ordered) {
            Map<String, Object> scoped = context.getNodeScopedOutput(predecessorId);
            if (scoped.isEmpty()) {
                continue;
            }
            // 保留 envelope 原貌存入 scopedOutputs（便于 status 查询、message 透传）
            workspace.getScopedOutputs().put(predecessorId, new LinkedHashMap<>(scoped));
            // 扁平 variables 注入时解包 envelope.data，避免 {nodeId}__status / {nodeId}__message 污染
            Map<String, Object> dataView = unwrapDataView(scoped);
            for (Map.Entry<String, Object> e : dataView.entrySet()) {
                String scopedKey = predecessorId + NODE_FIELD_SEPARATOR + e.getKey();
                workspace.getVariables().put(scopedKey, e.getValue());
            }
        }

        // 3. 工作空间文件
        workspace.setFiles(scanWorkingDir(context.getWorkingDir()));

        return workspace;
    }

    /**
     * 解包节点输出的 data 视图。
     * <ul>
     *   <li>若是 envelope（含 status/data），返回内部 data Map</li>
     *   <li>否则返回原始 Map（legacy 兼容）</li>
     * </ul>
     */
    private static Map<String, Object> unwrapDataView(Map<String, Object> scoped) {
        if (scoped == null || scoped.isEmpty()) return Collections.emptyMap();
        if (NodeOutputEnvelope.isEnvelope(scoped)) {
            NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(scoped);
            return env == null ? Collections.emptyMap() : env.getData();
        }
        return scoped;
    }

    /**
     * 按 DAG 层级顺序排列节点 ID（先出现的层级在前）。
     */
    private List<String> orderByLevels(Set<String> nodeIds, DagResolver.ResolvedDag dag) {
        List<String> result = new ArrayList<>();
        for (List<String> level : dag.getLevels()) {
            for (String nodeId : level) {
                if (nodeIds.contains(nodeId)) {
                    result.add(nodeId);
                }
            }
        }
        return result;
    }

    /**
     * 扫描工作目录，返回经 .agentignore 过滤后的相对路径列表。
     */
    private List<String> scanWorkingDir(Path workingDir) {
        if (workingDir == null || !Files.isDirectory(workingDir)) {
            return Collections.emptyList();
        }
        try (Stream<Path> walk = Files.walk(workingDir)) {
            return walk
                    .filter(p -> shouldInclude(workingDir, p))
                    .map(p -> workingDir.relativize(p).toString().replace('\\', '/'))
                    .sorted()
                    .collect(Collectors.toList());
        } catch (IOException e) {
            return Collections.emptyList();
        }
    }

    private boolean shouldInclude(Path workingDir, Path p) {
        if (Files.isDirectory(p)) {
            return false;
        }
        Path rel = workingDir.relativize(p);
        String relStr = rel.toString().replace('\\', '/');
        if (agentIgnoreFilter.shouldIgnore(relStr, false)) {
            return false;
        }
        Path cur = rel;
        while (cur != null && cur.getNameCount() > 1) {
            cur = cur.getParent();
            if (cur == null) break;
            if (agentIgnoreFilter.shouldIgnore(cur.toString().replace('\\', '/'), true)) {
                return false;
            }
        }
        return true;
    }
}
