// Package dsl 把前端导出的 JSON DSL 图编译为可执行的对话计划。
// 当前从图中抽取「系统提示词 / 用户输入 / 工具节点」；后续可演进为
// compose.NewGraph 的完整多节点编译（分支/并行/工具节点逐一映射）。
package dsl

import (
	"encoding/json"
	"fmt"
	"strings"
)

// Node 是 DSL 图的一个节点（与前端 exportDsl 对齐）。
type Node struct {
	ID     string         `json:"id"`
	Kind   string         `json:"kind"`
	Label  string         `json:"label"`
	Config map[string]any `json:"config"`
}

// Edge 是一条连线。SourceHandle 标记从 branch 节点引出的边走真/假分支（"true"/"false"）。
type Edge struct {
	Source       string `json:"source"`
	Target       string `json:"target"`
	SourceHandle string `json:"sourceHandle"`
}

// Flow 是整张图。
type Flow struct {
	Version string `json:"version"`
	Nodes   []Node `json:"nodes"`
	Edges   []Edge `json:"edges"`
}

// Plan 是编译后的对话计划。
type Plan struct {
	System string   // 系统提示词（来自 agent 节点的 system；空则默认）
	Query  string   // 用户输入（来自 input 节点 text / agent 节点 prompt）
	Tools  []string // 图中工具节点绑定的 MCP 工具名（供后续工具编排用）
}

const defaultSystem = "你是 sundynix-agentix 平台的 AI 助手。"

// Parse 把 DSL 原文解析为图结构。
func Parse(graph json.RawMessage) (*Flow, error) {
	var f Flow
	if err := json.Unmarshal(graph, &f); err != nil {
		return nil, err
	}
	return &f, nil
}

// Topo 返回节点的拓扑序（Kahn）；无边/有环时退化为声明顺序。
func (f *Flow) Topo() []Node {
	byID := make(map[string]Node, len(f.Nodes))
	indeg := make(map[string]int, len(f.Nodes))
	adj := make(map[string][]string)
	for _, n := range f.Nodes {
		byID[n.ID] = n
		indeg[n.ID] = 0
	}
	for _, e := range f.Edges {
		if _, ok := byID[e.Source]; !ok {
			continue
		}
		if _, ok := byID[e.Target]; !ok {
			continue
		}
		adj[e.Source] = append(adj[e.Source], e.Target)
		indeg[e.Target]++
	}
	var queue, order []string
	for _, n := range f.Nodes { // 按声明序入队，保证确定性
		if indeg[n.ID] == 0 {
			queue = append(queue, n.ID)
		}
	}
	for len(queue) > 0 {
		id := queue[0]
		queue = queue[1:]
		order = append(order, id)
		for _, t := range adj[id] {
			indeg[t]--
			if indeg[t] == 0 {
				queue = append(queue, t)
			}
		}
	}
	out := make([]Node, 0, len(f.Nodes))
	seen := make(map[string]bool)
	for _, id := range order {
		out = append(out, byID[id])
		seen[id] = true
	}
	for _, n := range f.Nodes { // 有环时补齐剩余
		if !seen[n.ID] {
			out = append(out, n)
		}
	}
	return out
}

// Compile 解析 DSL 图，抽取对话计划。无法解析时退化为把原文当输入（兼容旧行为）。
func Compile(graph json.RawMessage) Plan {
	f, err := Parse(graph)
	if err != nil || len(f.Nodes) == 0 {
		return Plan{System: defaultSystem, Query: strings.TrimSpace(string(graph))}
	}

	var queries, systems, tools []string
	for _, n := range f.Nodes {
		switch n.Kind {
		case "input":
			if t := str(n.Config["text"]); t != "" {
				queries = append(queries, t)
			}
		case "agent":
			if s := str(n.Config["system"]); s != "" {
				systems = append(systems, s)
			}
			if p := str(n.Config["prompt"]); p != "" { // 单 agent 节点快速测试时直接带 prompt
				queries = append(queries, p)
			}
		case "tool":
			if t := str(n.Config["tool"]); t != "" {
				tools = append(tools, t)
			}
		}
	}

	system := strings.Join(systems, "\n")
	if system == "" {
		system = defaultSystem
	}
	query := strings.Join(queries, "\n")
	if query == "" {
		query = "你好" // 无结构化输入时的兜底，避免给模型发空消息
	}
	return Plan{System: system, Query: query, Tools: tools}
}

// ToolBinding 从 tool/retriever 节点抽取要调用的 MCP 工具名与参数。
// 非工具型节点返回空工具名（由编译器跳过）。
func ToolBinding(n Node) (tool string, args map[string]any) {
	args = map[string]any{}
	switch n.Kind {
	case "tool":
		tool = str(n.Config["tool"])
		if raw := str(n.Config["args"]); raw != "" {
			_ = json.Unmarshal([]byte(raw), &args) // 前端 args 为 JSON 字符串
		}
	case "retriever":
		// 检索节点暂映射到 wiki_search；RAG 接真后改 memory_search / 真实混合检索。
		tool = "wiki_search"
		if kb := str(n.Config["kb"]); kb != "" {
			args["kb"] = kb
		}
	}
	return tool, args
}

func str(v any) string {
	if v == nil {
		return ""
	}
	if s, ok := v.(string); ok {
		return strings.TrimSpace(s)
	}
	return strings.TrimSpace(fmt.Sprint(v))
}
