package eino

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/cloudwego/eino/components/prompt"
	"github.com/cloudwego/eino/schema"
)

// RunCtx 是组装模型消息用的上下文：图解释器（graph.go）把黑板汇总到它，
// buildMessages 据此拼出发给模型的消息序列。用统一结构避免散落多处拼装。
type RunCtx struct {
	UserID    string
	SessionID string
	System    string            // Agent 节点系统提示词
	Query     string            // 用户输入
	Profile   string            // 召回的画像
	History   []*schema.Message // 短期历史
	ToolOut   []string          // 工具/检索节点产出（含参考资料）
	Upstream  []string          // 前序协作 agent 的产出（多 agent 接力时注入，让下游基于上游继续）
}

// chatTemplate 是会话消息模板：系统提示词 + 历史占位 + 用户输入。
// 用 Eino components/prompt.ChatTemplate（FString 仅解析模板串、值原样注入，故 JSON 花括号安全）。
var chatTemplate = prompt.FromMessages(schema.FString,
	schema.SystemMessage("{system}"),
	schema.MessagesPlaceholder("history", true),
	schema.UserMessage("{query}"),
)

// buildMessages 把上下文组装为发给模型的消息序列（系统提示词 + 画像 + 工具产出 + 历史 + 用户输入）。
// 系统串的动态拼装（按需注入画像/参考资料）留在 Go 侧；最终经 ChatTemplate 成型。
func buildMessages(ctx context.Context, rc *RunCtx) ([]*schema.Message, error) {
	var sys strings.Builder
	sys.WriteString(rc.System)
	if rc.Profile != "" {
		sys.WriteString("\n\n关于当前用户的已知信息：\n")
		sys.WriteString(rc.Profile)
		sys.WriteString("\n请据此个性化作答并保持其偏好。")
	}
	if len(rc.ToolOut) > 0 {
		sys.WriteString("\n\n以下是工具/检索得到的参考资料：\n")
		sys.WriteString(strings.Join(rc.ToolOut, "\n---\n"))
	}
	if len(rc.Upstream) > 0 {
		sys.WriteString("\n\n以下是前序协作 agent 的产出，请在此基础上继续完成你的部分（不要重头再来）：\n")
		sys.WriteString(strings.Join(rc.Upstream, "\n---\n"))
	}
	// 防御：剔除空 content 的历史消息。OpenAI 兼容 API 拒绝「content 与 tool_calls 都为空」的
	// assistant 消息（400 Invalid assistant message），一条脏历史会毒化整段会话的后续请求。
	history := make([]*schema.Message, 0, len(rc.History))
	for _, m := range rc.History {
		if m == nil || (strings.TrimSpace(m.Content) == "" && len(m.ToolCalls) == 0) {
			continue
		}
		history = append(history, m)
	}
	return chatTemplate.Format(ctx, map[string]any{
		"system":  sys.String(),
		"history": history,
		"query":   rc.Query,
	})
}

// previewArgs 把工具入参压成一行短预览。
func previewArgs(args map[string]any) string {
	if data, err := json.Marshal(args); err == nil {
		return truncate(string(data), 120)
	}
	return ""
}
