package com.agent.management.engine.executor;

import com.agent.management.engine.AgentIgnoreFilter;
import com.agent.management.engine.NodeExecutionResult;
import com.agent.management.engine.NodeExecutor;
import com.agent.management.engine.NodeInputResolver;
import com.agent.management.engine.NodeOutputEnvelope;
import com.agent.management.engine.NodeWorkspace;
import com.agent.management.engine.WorkflowContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 输出节点执行器
 *
 * <p>语义：输出节点按画布声明的 {@code data.fields[]} 进行投影输出，每个 field 既是从前驱取值的"输入变量"，
 * 也是写入最终结果的"输出变量"。投影逻辑复用 {@link NodeInputResolver}，享受与 LLM/Agent/Skill 节点
 * 一致的能力：显式 mapping 优先 + 四级隐式回退（精确同名 → 忽略 -_/大小写 → JSON 路径精确 → JSON 路径模糊），
 * 以及按声明 type 的类型转换。</p>
 *
 * <ul>
 *   <li>{@code data.fields[]} 每一项作为输入字段解析：field.name 同时作为输出 key（裸名，不带 predId__ 前缀）</li>
 *   <li>未声明 fields：返回空 data（用户决策：严格语义）</li>
 *   <li>required 字段缺失：返回 failure envelope</li>
 *   <li>{@code _workingDirFiles} / {@code _runId} 作为元数据额外注入（用户决策：全部保留）</li>
 * </ul>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OutputExecutor implements NodeExecutor {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final AgentIgnoreFilter agentIgnoreFilter;

    @Override
    public String getType() {
        return "output";
    }

    @Override
    public NodeExecutionResult execute(String nodeId, JsonNode data, WorkflowContext context, NodeWorkspace workspace) {
        String outputType = data.path("outputType").asText("text");

        // 按 data.fields 投影：把 fields 当作 inputs 解析（复用 NodeInputResolver 的 mapping + 四级模糊匹配 + 类型转换）
        Map<String, Object> projectedData = new LinkedHashMap<>();
        JsonNode fieldsNode = data.path("fields");
        if (fieldsNode.isArray() && fieldsNode.size() > 0) {
            // 包装成 {inputs: [...fields]} 复用 NodeInputResolver 的解析路径
            ObjectNode fieldsAsInputs = MAPPER.createObjectNode();
            fieldsAsInputs.set("inputs", fieldsNode);
            NodeInputResolver.ResolvedInputs resolved =
                    NodeInputResolver.resolveInputs(fieldsAsInputs, workspace);

            if (resolved.hasMissing()) {
                String msg = "输出节点缺少必填字段：" + String.join("、", resolved.getMissing());
                log.warn("[Output] 节点 {} 必填校验失败：{}", nodeId, resolved.getMissing());
                return NodeExecutionResult.success(nodeId,
                        NodeOutputEnvelope.failure(msg).toMap());
            }

            if (!resolved.getWarnings().isEmpty()) {
                resolved.getWarnings().forEach(w ->
                        log.warn("[Output] 节点 {} 输入解析警告: {}", nodeId, w));
            }

            projectedData.putAll(resolved.getValues());
        }
        // 未声明 fields：projectedData 保持为空（严格语义）

        // 元数据注入（用户决策：全部保留）
        Path workingDir = context.getWorkingDir();
        if (workingDir != null && Files.isDirectory(workingDir)) {
            try {
                List<String> files = Files.walk(workingDir)
                        .filter(p -> shouldInclude(workingDir, p))
                        .map(p -> workingDir.relativize(p).toString().replace('\\', '/'))
                        .collect(Collectors.toList());
                if (!files.isEmpty()) {
                    projectedData.put("_workingDirFiles", files);
                }
            } catch (IOException e) {
                log.warn("[Output] 扫描工作目录失败: {}", e.getMessage());
            }
        }
        projectedData.put("_runId", context.getRunId());

        log.info("[Output] 节点 {} 输出类型={}, 变量数={}", nodeId, outputType, projectedData.size());
        return NodeExecutionResult.success(nodeId,
                NodeOutputEnvelope.success("工作流执行成功", projectedData).toMap());
    }

    /**
     * 判断路径是否应保留在文件列表中。
     * 同时跳过目录本身、.agentignore 命中规则（按路径或任意祖先目录命中均算）。
     */
    private boolean shouldInclude(Path workingDir, Path p) {
        if (Files.isDirectory(p)) return false;
        // 检查路径自身及其每个祖先目录是否被 .agentignore 命中
        Path rel = workingDir.relativize(p);
        String relStr = rel.toString().replace('\\', '/');
        // 自身（作为文件）
        if (agentIgnoreFilter.shouldIgnore(relStr, false)) return false;
        // 各级祖先目录（如 .hermes/skills/foo.md 需要被 .hermes/ 命中）
        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;
    }
}
