package main

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

const managerSystemPrompt = `You are the Manager agent. You coordinate between the user, a Programmer agent, and a QA agent.

You have two orchestration tools:
- assign_task(instructions): delegate coding work to the Programmer. Returns the Programmer's report.
- request_qa_review(focus): ask QA to validate specific files/changes. Returns QA's report.

Workflow:
1. Read the user's request. If it is purely conversational or meta (a greeting, a question about the agent itself), answer directly with no tool calls.
2. Otherwise, plan the work, then call assign_task with clear, self-contained instructions for the Programmer.
3. When the Programmer reports back, call request_qa_review naming the files touched and what QA should verify.
4. If QA reports issues, call assign_task again with specific fix instructions that reference QA's findings.
5. Iterate until QA approves, then reply to the user with a concise summary of what changed and any caveats.

Important:
- You have NO file tools. Do not try to read or edit files yourself.
- Every call to assign_task/request_qa_review spawns a fresh sub-agent with no memory of prior calls — put all needed context into the brief.
- Keep the final user-facing reply brief: what was done, where, and anything flagged by QA.`

func managerTools() []Tool {
	return []Tool{
		{
			Type: "function",
			Function: ToolFunction{
				Name:        "assign_task",
				Description: "Delegate a coding task to the Programmer agent. The Programmer has read/create/edit/delete/list file tools scoped to the project root. Returns the Programmer's written report.",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"instructions": map[string]any{
							"type":        "string",
							"description": "Clear, self-contained instructions for the Programmer. Include all context needed (files, goals, constraints).",
						},
					},
					"required": []string{"instructions"},
				},
			},
		},
		{
			Type: "function",
			Function: ToolFunction{
				Name:        "request_qa_review",
				Description: "Ask the QA agent to review code. QA has read-only file tools (read_file, list_directory) plus a shell command runner (run_command) that can execute any test suite, linter, type-checker, or build command. Returns QA's written report with a verdict.",
				Parameters: map[string]any{
					"type": "object",
					"properties": map[string]any{
						"focus": map[string]any{
							"type":        "string",
							"description": "What QA should review: specific files, what aspect of the change, what to look for. Be explicit.",
						},
					},
					"required": []string{"focus"},
				},
			},
		},
	}
}

type Orchestrator struct {
	client  *Client
	root    string
	manager *Agent
	history []Message
}

func NewOrchestrator(client *Client, root string) *Orchestrator {
	o := &Orchestrator{client: client, root: root}
	o.manager = &Agent{
		Name:         "manager",
		Client:       client,
		SystemPrompt: managerSystemPrompt,
		Tools:        managerTools(),
		ToolExec:     o.execManagerTool,
		OnToolCall:   logToolCall(""),
	}
	o.history = []Message{{Role: "system", Content: managerSystemPrompt}}
	return o
}

// Handle advances the Manager conversation with one user turn.
func (o *Orchestrator) Handle(ctx context.Context, userInput string) (string, error) {
	o.history = append(o.history, Message{Role: "user", Content: userInput})
	reply, updated, err := o.manager.Run(ctx, o.history)
	o.history = updated
	return reply, err
}

func (o *Orchestrator) execManagerTool(ctx context.Context, name, argsJSON string) string {
	switch name {
	case "assign_task":
		var a struct {
			Instructions string `json:"instructions"`
		}
		if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
			return "error: invalid arguments: " + err.Error()
		}
		if strings.TrimSpace(a.Instructions) == "" {
			return "error: instructions is required"
		}
		prog := newProgrammerAgent(o.client, o.root, logToolCall("  "))
		report, err := prog.Do(ctx, a.Instructions)
		if err != nil {
			return "error: programmer failed: " + err.Error()
		}
		printReport("programmer", report)
		return report

	case "request_qa_review":
		var a struct {
			Focus string `json:"focus"`
		}
		if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
			return "error: invalid arguments: " + err.Error()
		}
		if strings.TrimSpace(a.Focus) == "" {
			return "error: focus is required"
		}
		qa := newQAAgent(o.client, o.root, logToolCall("  "))
		report, err := qa.Do(ctx, a.Focus)
		if err != nil {
			return "error: qa failed: " + err.Error()
		}
		printReport("qa", report)
		return report

	default:
		return "error: unknown tool " + name
	}
}

func logToolCall(indent string) func(string, ToolCall, string) {
	return func(agent string, tc ToolCall, _ string) {
		fmt.Printf("%s[%s] %s %s\n", indent, agent, tc.Function.Name, tc.Function.Arguments)
	}
}

func printReport(from, report string) {
	fmt.Printf("  [%s → manager]\n", from)
	for _, line := range strings.Split(strings.TrimRight(report, "\n"), "\n") {
		fmt.Printf("    %s\n", line)
	}
}
