package com.agent.management.engine;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;

/**
 * 节点类型（node.type）规范化工具。
 *
 * <p><b>权威约定</b>：所有节点 type 必须是 camelCase 首字母小写形式，
 * 例如 {@code userInput}、{@code llm}、{@code smartAction}、{@code condition}、{@code output}、{@code agent}。</p>
 *
 * <p>本类提供两道核心防御：</p>
 * <ul>
 *   <li>{@link #normalize(String)} —— 单类型字符串规范化</li>
 *   <li>{@link #normalizeGraphData(String)} —— graphData JSON 中所有节点 type 规范化</li>
 * </ul>
 *
 * <p>所有 graphData 的写入入口（Service.updateWorkflow、VersionService.createSnapshot）必须
 * 调用 {@link #normalizeGraphData}；读取入口（DagResolver）也做一次防御性规范化。</p>
 */
@Slf4j
public final class NodeTypeUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private NodeTypeUtils() {}

    /**
     * 把单个节点类型字符串规范化为 camelCase 首字母小写形式。
     * null/空字符串原样返回；已规范化的字符串不重复处理。
     */
    public static String normalize(String type) {
        if (type == null || type.isEmpty()) return type;
        if (Character.isLowerCase(type.charAt(0))) return type;
        return Character.toLowerCase(type.charAt(0)) + type.substring(1);
    }

    /**
     * 对 graphData JSON 中所有节点的 type 字段做规范化。
     *
     * <p>规则：</p>
     * <ul>
     *   <li>解析失败 → 返回原字符串（不破坏数据），并记 warn 日志</li>
     *   <li>未发生变化 → 返回原字符串对象（节省写入，便于上层"脏检测"）</li>
     *   <li>发生变化 → 返回新 JSON 字符串</li>
     * </ul>
     *
     * @param graphData 工作流图数据 JSON 字符串，可为 null
     * @return 规范化后的 JSON 字符串；入参为 null 时返回 null
     */
    public static String normalizeGraphData(String graphData) {
        if (graphData == null || graphData.isBlank()) return graphData;
        try {
            JsonNode root = MAPPER.readTree(graphData);
            JsonNode nodes = root.path("nodes");
            if (!nodes.isArray() || nodes.isEmpty()) return graphData;

            boolean changed = false;
            ArrayNode newNodes = MAPPER.createArrayNode();
            for (JsonNode node : nodes) {
                String type = node.path("type").asText("");
                String normalized = normalize(type);
                if (!normalized.equals(type)) {
                    ObjectNode mutable = node.deepCopy();
                    mutable.put("type", normalized);
                    newNodes.add(mutable);
                    changed = true;
                } else {
                    newNodes.add(node);
                }
            }

            if (!changed) return graphData;

            ObjectNode newRoot = root.deepCopy();
            newRoot.set("nodes", newNodes);
            return MAPPER.writeValueAsString(newRoot);
        } catch (Exception e) {
            log.warn("[NodeTypeUtils] graphData 解析失败，跳过规范化: {}", e.getMessage());
            return graphData;
        }
    }

    /**
     * 判断 graphData 是否已规范化（用于启动时迁移做脏检测，避免无意义写入）。
     */
    public static boolean isNormalized(String graphData) {
        if (graphData == null || graphData.isBlank()) return true;
        try {
            JsonNode root = MAPPER.readTree(graphData);
            JsonNode nodes = root.path("nodes");
            if (!nodes.isArray()) return true;
            for (JsonNode node : nodes) {
                String type = node.path("type").asText("");
                if (!type.isEmpty() && Character.isUpperCase(type.charAt(0))) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            return true; // 解析失败不视为脏数据
        }
    }

    /**
     * 根据节点 data.outputs 声明解析输出变量名（用于单输出场景）。
     *
     * <p>规则：</p>
     * <ul>
     *   <li>单输出（outputs 长度 = 1）：返回 outputs[0].name</li>
     *   <li>未声明 outputs（空数组或非数组）：返回 defaultName</li>
     *   <li>多输出（outputs 长度 &gt; 1）：本期仅取 outputs[0].name，并记录警告日志；
     *       Agent/SmartAction/Hermes 系列节点不支持结构化多输出。
     *       LLM 节点不走此路径，由 {@link StructuredOutputHelper} 单独处理。</li>
     * </ul>
     *
     * @param data        节点 data 子树（JSON）
     * @param defaultName outputs 为空时的默认变量名（通常为 "result"）
     * @return 输出变量名
     */
    public static String resolveOutputVarName(JsonNode data, String defaultName) {
        JsonNode outputs = data.path("outputs");
        if (outputs.isArray() && outputs.size() > 0) {
            String name = outputs.get(0).path("name").asText("");
            if (!name.isEmpty()) {
                if (outputs.size() > 1) {
                    log.warn("[NodeTypeUtils] 节点声明了多个 outputs，本期仅取第一个: {}", name);
                }
                return name;
            }
        }
        return defaultName;
    }

    /**
     * 构造单输出 Map：{@code { varName: result }}。
     */
    public static Map<String, Object> singleOutput(String varName, Object result) {
        return Map.of(varName, result);
    }

    /**
     * 判断源类型是否可以赋值给目标类型（与前端 ioInference.isTypeCompatible 保持一致）。
     *
     * <p>规则：</p>
     * <ul>
     *   <li>类型相同 → 兼容</li>
     *   <li>filePath / directoryPath / number / boolean → 可隐式转 string</li>
     *   <li>array → 兼容 object</li>
     *   <li>其他组合 → 不兼容</li>
     * </ul>
     */
    public static boolean isTypeCompatible(String sourceType, String targetType) {
        if (sourceType == null || targetType == null) return true; // 未声明类型时跳过校验
        if (sourceType.equals(targetType)) return true;
        // 以下类型可隐式转为 string
        java.util.Set<String> stringLike = java.util.Set.of("filePath", "directoryPath", "number", "boolean");
        if (stringLike.contains(sourceType) && "string".equals(targetType)) return true;
        // array → object 兼容
        if ("array".equals(sourceType) && "object".equals(targetType)) return true;
        return false;
    }

    /**
     * 推断 Java 对象在 IO 字段类型系统中的类型名（用于前置条件类型校验）。
     */
    public static String inferTypeName(Object value) {
        if (value == null) return null;
        if (value instanceof Number) return "number";
        if (value instanceof Boolean) return "boolean";
        if (value instanceof java.util.Collection<?>) return "array";
        if (value instanceof Map<?, ?>) return "object";
        return "string";
    }
}
