package newagentprompt

import (
	"fmt"
	"strings"

	newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
	"github.com/cloudwego/eino/schema"
)

// ConversationTurn 表示对话历史中的一轮自然语言交互。
//
// 职责边界：
// 1. 这里只承载 user 与 assistant speak，不承载 tool_call 和 tool observation；
// 2. 供 chat / plan / deliver 等节点复用，避免各节点重复写一套提取逻辑；
// 3. 不负责裁剪长度，长度预算统一交给压缩层处理。
type ConversationTurn struct {
	Role    string
	Content string
}

// StageMessagesConfig 描述统一四段式骨架下，各节点自行提供的内容块。
//
// 设计目标：
// 1. 统一层只负责“四条消息怎么拼”，不再替节点决定“每条消息里该放什么”；
// 2. Msg1 / Msg2 / Msg3Prefix / Msg3Suffix 都由节点自己渲染，避免 chat / plan / deliver 继续套 execute 的内容模板；
// 3. memory_context 仍由统一层单入口注入到 msg3，避免多处重复注入。
type StageMessagesConfig struct {
	// SystemPrompt 是节点自己的系统提示词。
	SystemPrompt string

	// Msg1Content 是第 2 条 assistant 消息，通常放“节点想看的历史视图”。
	Msg1Content string

	// Msg2Content 是第 3 条 assistant 消息，通常放“节点自己的工作区/补充约束”。
	Msg2Content string

	// Msg3Prefix 是第 4 条消息中位于 memory_context 之前的内容。
	// 常见放法：阶段状态、规划工作区摘要、交付收口约束等。
	Msg3Prefix string

	// Msg3Suffix 是第 4 条消息中位于 memory_context 之后的内容。
	// 对 user-role 节点来说，这里通常放最终用户指令，保证“用户输入收尾”。
	Msg3Suffix string

	// Msg3Role 指定第 4 条消息的角色。
	// Execute 继续使用 system，其余节点一般使用 user。
	Msg3Role schema.RoleType

	// SkipBaseSystemPrompt 为 true 时，msg0 只使用节点自己的 SystemPrompt，
	// 不再拼接 ConversationContext.SystemPrompt。
	SkipBaseSystemPrompt bool

	// UseLiteToolCatalogMsg 为 true 时，msg0 工具目录采用轻量模式（仅名称与职责）。
	UseLiteToolCatalogMsg bool
}

// buildUnifiedStageMessages 组装统一 4 段式消息骨架。
//
// 固定布局：
// 1. msg0(system)：系统规则 + 阶段规则 + 工具简表；
// 2. msg1(assistant)：节点自定义的历史视图；
// 3. msg2(assistant)：节点自定义的工作区；
// 4. msg3(user/system)：节点自定义前后缀 + 统一 memory_context。
func buildUnifiedStageMessages(
	ctx *newagentmodel.ConversationContext,
	config StageMessagesConfig,
) []*schema.Message {
	msg0 := buildUnifiedMsg0(config.SystemPrompt, ctx, config.SkipBaseSystemPrompt, config.UseLiteToolCatalogMsg)
	msg1 := buildUnifiedMsg1(config.Msg1Content)
	msg2 := buildUnifiedMsg2(config.Msg2Content)
	msg3 := buildUnifiedMsg3(ctx, config)

	return []*schema.Message{
		schema.SystemMessage(msg0),
		{Role: schema.Assistant, Content: msg1},
		{Role: schema.Assistant, Content: msg2},
		buildUnifiedMsg3Message(msg3, config.Msg3Role),
	}
}

// buildUnifiedMsg3Message 根据配置决定第 4 条消息的角色。
func buildUnifiedMsg3Message(content string, role schema.RoleType) *schema.Message {
	if role == schema.User {
		return schema.UserMessage(content)
	}
	return schema.SystemMessage(content)
}

// buildUnifiedMsg0 合并系统提示 + 工具简表，生成 msg0。
//
// 步骤化说明：
// 1. 先合并基础系统提示与节点系统提示，保证模型身份稳定；
// 2. 若当前节点注入了工具 schema，则附加紧凑工具目录；
// 3. 若两部分都为空，则回退到最小兜底提示，避免出现空消息。
func buildUnifiedMsg0(stageSystemPrompt string, ctx *newagentmodel.ConversationContext, skipBaseSystemPrompt bool, useLiteToolCatalog bool) string {
	base := ""
	if skipBaseSystemPrompt {
		base = strings.TrimSpace(stageSystemPrompt)
	} else {
		base = strings.TrimSpace(mergeSystemPrompts(ctx, stageSystemPrompt))
	}
	if base == "" {
		base = "你是 SmartMate 助手，请继续当前阶段。"
	}

	toolCatalog := renderExecuteToolCatalogCompact(ctx, nil)
	if useLiteToolCatalog {
		toolCatalog = renderUnifiedToolCatalogLite(ctx)
	}
	if toolCatalog == "" {
		return base
	}
	return base + "\n\n" + toolCatalog
}

// renderUnifiedToolCatalogLite 渲染统一阶段可用工具的轻量目录。
//
// 1. 只展示工具名和一句话职责，避免把 execute 的参数/返回示例污染到 plan/chat/deliver。
// 2. 目录信息仅用于“能力边界感知”，不承担具体参数指导。
// 3. 当工具数量过多时保留前若干项并给出省略提示，控制 msg0 体积。
func renderUnifiedToolCatalogLite(ctx *newagentmodel.ConversationContext) string {
	if ctx == nil {
		return ""
	}

	schemas := ctx.ToolSchemasSnapshot()
	if len(schemas) == 0 {
		return ""
	}

	const maxItems = 18
	lines := []string{"当前可用工具（轻量目录）："}
	added := 0

	for _, item := range schemas {
		name := strings.TrimSpace(item.Name)
		if name == "" {
			continue
		}
		desc := strings.TrimSpace(item.Desc)
		if desc == "" {
			lines = append(lines, fmt.Sprintf("- %s", name))
		} else {
			lines = append(lines, fmt.Sprintf("- %s：%s", name, desc))
		}
		added++
		if added >= maxItems {
			break
		}
	}

	if added == 0 {
		return ""
	}
	if len(schemas) > added {
		lines = append(lines, fmt.Sprintf("- 其余 %d 个工具已省略（按需再看）。", len(schemas)-added))
	}
	return strings.Join(lines, "\n")
}

// buildUnifiedMsg1 返回节点自行提供的历史视图。
//
// 说明：
// 1. 统一层不再内置 execute 风格的 ReAct 摘要；
// 2. 节点若未传入内容，则回退到最小占位，保证四段结构稳定；
// 3. 压缩层仍会统一统计和压缩这条消息。
func buildUnifiedMsg1(content string) string {
	content = strings.TrimSpace(content)
	if content != "" {
		return content
	}
	return "历史上下文：暂无。"
}

// buildUnifiedMsg2 返回节点自行提供的工作区。
//
// 说明：
// 1. 非 execute 节点也允许有自己的 msg2，不再被统一层硬塞“暂无”语义；
// 2. 若节点暂时没有额外工作区，则回退到最小占位，保证结构稳定。
func buildUnifiedMsg2(content string) string {
	content = strings.TrimSpace(content)
	if content != "" {
		return content
	}
	return "阶段工作区：暂无。"
}

// buildUnifiedMsg3 统一拼装 msg3：前缀 + memory_context + 后缀。
//
// 步骤化说明：
// 1. 前缀由节点决定，适合放轻量状态或阶段约束；
// 2. memory_context 只在这里注入一次，避免 pinned block 多入口重复出现；
// 3. 后缀由节点决定。对于 user-role 节点，通常把最终用户指令放在这里，保证消息末尾仍是用户输入。
func buildUnifiedMsg3(ctx *newagentmodel.ConversationContext, config StageMessagesConfig) string {
	var sections []string

	if prefix := strings.TrimSpace(config.Msg3Prefix); prefix != "" {
		sections = append(sections, prefix)
	}
	if memoryText := renderUnifiedMemoryContext(ctx); memoryText != "" {
		sections = append(sections, "相关记忆（仅在确有帮助时参考，不要机械复述）：\n"+memoryText)
	}
	if suffix := strings.TrimSpace(config.Msg3Suffix); suffix != "" {
		sections = append(sections, suffix)
	}

	if len(sections) == 0 {
		return "请继续当前阶段。"
	}
	return strings.Join(sections, "\n\n")
}

// renderUnifiedMemoryContext 提取需要补充到 msg3 的记忆文本。
//
// 步骤化说明：
// 1. 只消费 memory_context，避免把 execution_context / current_step 等阶段专属块混回 prompt；
// 2. block 不存在或正文为空时直接返回空串；
// 3. 这里只读取 agentsvc 已经产出的最终文本，不在这里重新拼装记忆。
func renderUnifiedMemoryContext(ctx *newagentmodel.ConversationContext) string {
	if ctx == nil {
		return ""
	}

	block, ok := ctx.PinnedBlockByKey("memory_context")
	if !ok {
		return ""
	}
	content := strings.TrimSpace(block.Content)
	if content == "" {
		return ""
	}
	return content
}

// CollectConversationTurns 从历史消息中提取 user + assistant speak 对话流。
//
// 提取规则：
// 1. 只保留 user 消息（排除 correction prompt）和 assistant 纯文本消息；
// 2. assistant tool_call 消息与 tool observation 消息不纳入“真实对话”；
// 3. 返回顺序保持与原始 history 一致。
func CollectConversationTurns(history []*schema.Message) []ConversationTurn {
	if len(history) == 0 {
		return nil
	}

	turns := make([]ConversationTurn, 0, len(history))
	for _, msg := range history {
		if msg == nil {
			continue
		}
		text := strings.TrimSpace(msg.Content)
		if text == "" {
			continue
		}
		switch msg.Role {
		case schema.User:
			// 1. 跳过后端注入的 correction prompt，避免把纠错文案误判为用户真实意图。
			if isExecuteCorrectionPrompt(msg) {
				continue
			}
			turns = append(turns, ConversationTurn{Role: "user", Content: text})
		case schema.Assistant:
			// 2. 跳过工具调用消息，只保留真正面向用户的 speak/答复。
			if len(msg.ToolCalls) > 0 {
				continue
			}
			turns = append(turns, ConversationTurn{Role: "assistant", Content: text})
		}
	}

	return turns
}
