package server

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

	"github.com/daniel/cursor-adapter/internal/sanitize"
	"github.com/daniel/cursor-adapter/internal/types"
)

// buildPromptFromAnthropicMessages flattens an Anthropic Messages request into
// a single prompt string suitable for `agent --print`. It:
//   - renders tool_use / tool_result blocks as readable pseudo-XML so the
//     model can follow the trajectory of previous tool calls
//   - embeds the `tools` schema as part of the System block via
//     toolsToSystemText, so the model knows what tools the outer agent (e.g.
//     Claude Code) has available
//   - runs every piece of free text through sanitize.Text to strip Claude Code
//     branding and telemetry headers that would confuse the Cursor agent
func buildPromptFromAnthropicMessages(req types.AnthropicMessagesRequest) string {
	var systemParts []string
	for _, block := range req.System {
		if block.Type == "text" && strings.TrimSpace(block.Text) != "" {
			systemParts = append(systemParts, sanitize.Text(block.Text))
		}
	}
	if tools := toolsToSystemText(req.Tools); tools != "" {
		systemParts = append(systemParts, tools)
	}

	var convo []string
	for _, msg := range req.Messages {
		text := anthropicContentToText(msg.Content)
		if text == "" {
			continue
		}
		switch msg.Role {
		case "assistant":
			convo = append(convo, "Assistant: "+text)
		default:
			convo = append(convo, "User: "+text)
		}
	}

	var prompt strings.Builder
	if len(systemParts) > 0 {
		prompt.WriteString("System:\n")
		prompt.WriteString(strings.Join(systemParts, "\n\n"))
		prompt.WriteString("\n\n")
	}
	prompt.WriteString(strings.Join(convo, "\n\n"))
	prompt.WriteString("\n\nAssistant:")
	return prompt.String()
}

// anthropicContentToText renders a single message's content blocks as a
// single string. Unlike the old implementation, this one preserves tool_use
// and tool_result blocks so the model sees the full conversation trajectory
// rather than mysterious gaps.
func anthropicContentToText(content types.AnthropicContent) string {
	var parts []string
	for _, block := range content {
		switch block.Type {
		case "text":
			if block.Text != "" {
				parts = append(parts, sanitize.Text(block.Text))
			}
		case "tool_use":
			input := strings.TrimSpace(string(block.Input))
			if input == "" {
				input = "{}"
			}
			parts = append(parts, fmt.Sprintf(
				"<tool_use id=%q name=%q>\n%s\n</tool_use>",
				block.ID, block.Name, input,
			))
		case "tool_result":
			body := toolResultBody(block.Content)
			errAttr := ""
			if block.IsError {
				errAttr = ` is_error="true"`
			}
			parts = append(parts, fmt.Sprintf(
				"<tool_result tool_use_id=%q%s>\n%s\n</tool_result>",
				block.ToolUseID, errAttr, body,
			))
		case "image":
			parts = append(parts, "[Image]")
		case "document":
			title := block.Title
			if title == "" {
				title = "Document"
			}
			parts = append(parts, "[Document: "+title+"]")
		}
	}
	return strings.Join(parts, "\n")
}

// toolResultBody flattens the `content` field of a tool_result block, which
// can be either a plain string or an array of `{type, text}` content parts.
func toolResultBody(raw json.RawMessage) string {
	if len(raw) == 0 {
		return ""
	}

	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		return sanitize.Text(asString)
	}

	var parts []struct {
		Type string `json:"type"`
		Text string `json:"text"`
	}
	if err := json.Unmarshal(raw, &parts); err == nil {
		var out []string
		for _, p := range parts {
			if p.Type == "text" && p.Text != "" {
				out = append(out, sanitize.Text(p.Text))
			}
		}
		return strings.Join(out, "\n")
	}

	return string(raw)
}

// toolsToSystemText renders a tools schema array into a system-prompt chunk
// describing each tool. The idea (from cursor-api-proxy) is that since the
// Cursor CLI does not expose native tool_call deltas over the proxy, we tell
// the model what tools exist so it can reference them in its text output.
//
// NOTE: This is a one-way passthrough. The proxy cannot turn the model's
// textual "I would call Write with {...}" back into structured tool_use
// blocks. Callers that need real tool-use routing (e.g. Claude Code's coding
// agent) should run tools client-side and feed tool_result back in.
func toolsToSystemText(tools []types.AnthropicTool) string {
	if len(tools) == 0 {
		return ""
	}

	var lines []string
	lines = append(lines,
		"Available tools (they belong to the caller, not to you; describe your",
		"intended call in plain text and the caller will execute it):",
		"",
	)
	for _, t := range tools {
		schema := strings.TrimSpace(string(t.InputSchema))
		if schema == "" {
			schema = "{}"
		} else {
			var pretty any
			if err := json.Unmarshal(t.InputSchema, &pretty); err == nil {
				if out, err := json.MarshalIndent(pretty, "", "  "); err == nil {
					schema = string(out)
				}
			}
		}
		lines = append(lines,
			"Function: "+t.Name,
			"Description: "+sanitize.Text(t.Description),
			"Parameters: "+schema,
			"",
		)
	}
	return strings.TrimRight(strings.Join(lines, "\n"), "\n")
}
