package exec

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

const execExitCodeHelp = "Exit codes: validation=64, permission_denied=77, not_found=2, " +
	"conflict/agent_extension_catalog_stale/result_budget_exceeded=1, " +
	"provider_auth_unavailable/rate_limited/upstream_failure=69, internal=70"

const execResultBoundaryHelp = "Result boundary: complete success responses are <=64 KiB; read oversize responses return result_budget_exceeded with action-specific next guidance, while mutation oversize responses carry a compact applied receipt and no replay advice."

// RenderRootHelp returns the top-level human CLI help.
func (c *Catalog) RenderRootHelp() string {
	var b strings.Builder
	b.WriteString("Usage: agentteam exec <domain> <action> [<json|->]\n")
	b.WriteString("Schemas: agentteam exec <domain> <action> --input-schema | --output-schema\n")
	b.WriteString("Error schema: agentteam exec --error-schema\n")
	b.WriteString(execExitCodeHelp + "\n")
	b.WriteString(execResultBoundaryHelp + "\n\nActions:\n")
	for _, action := range c.actionsSnapshot() {
		b.WriteString("  " + action.identity() + " — " + oneLine(action.Summary) + "\n")
	}
	b.WriteString("\nRun `agentteam exec <domain> <action> --help` for one example.\n")
	return b.String()
}

// RenderDomainHelp returns CLI help for one registered domain.
func (c *Catalog) RenderDomainHelp(domain string) (string, bool) {
	var actions []actionDescriptor
	for _, action := range c.actionsSnapshot() {
		if action.Domain == domain {
			actions = append(actions, action)
		}
	}
	if len(actions) == 0 {
		return "", false
	}
	var b strings.Builder
	b.WriteString("Domain " + domain + "\n\nActions:\n")
	for _, action := range actions {
		b.WriteString("  " + action.Name + " — " + oneLine(action.Summary) + "\n")
	}
	b.WriteString("\nRun `agentteam exec <domain> <action> --help` for a usage example.\n")
	return b.String(), true
}

// RenderActionHelp returns human CLI help for one registered action.
func (c *Catalog) RenderActionHelp(domain, name string) (string, bool) {
	action, ok := c.lookup(domain, name)
	if !ok {
		return "", false
	}
	var b strings.Builder
	b.WriteString(action.identity() + " — " + oneLine(action.Summary) + "\n")
	b.WriteString("Usage: agentteam exec " + domain + " " + name + " <json|->\n")
	if len(action.Examples) > 0 {
		b.WriteString("Example: " + compactJSON(action.Examples[0].Input) + "\n")
	}
	if operations := workflowOperations(domain, name); operations != "" {
		b.WriteString("Operations: " + operations + "\n")
	}
	b.WriteString("Schemas: --input-schema | --output-schema\n")
	return b.String(), true
}

func workflowOperations(domain, name string) string {
	if domain != "workflow" {
		return ""
	}
	switch name {
	case "plan":
		return "list, show, validate, create, update, publish, archive"
	case "trigger":
		return "list, show, validate, create, update, enable, disable, archive"
	case "run":
		return "list, start, show, logs, cancel"
	case "step":
		return "list, get, complete, fail, pause, park"
	default:
		return ""
	}
}

// heredocDelimFor returns a heredoc delimiter that will never appear as its own
// line in the (single compact-JSON line) body, so the heredoc cannot be
// prematurely terminated. The body has no real newlines (json.Compact), so the
// base delimiter normally wins; the agentteam is a guarantee.
func heredocDelimFor(content string) string {
	d := "AGENTTEAMEXEC"
	for strings.Contains(content, "\n"+d+"\n") ||
		strings.HasPrefix(content, d+"\n") ||
		strings.HasSuffix(content, "\n"+d) ||
		content == d {
		d += "_"
	}
	return d
}

// oneLine collapses a summary/description to a single trimmed line.
func oneLine(s string) string {
	s = strings.TrimSpace(s)
	if i := strings.IndexAny(s, "\r\n"); i >= 0 {
		s = s[:i]
	}
	return strings.TrimSpace(s)
}

func compactJSON(raw json.RawMessage) string {
	if len(raw) == 0 {
		return "{}"
	}
	var buf bytes.Buffer
	if err := json.Compact(&buf, raw); err != nil {
		return string(raw)
	}
	return buf.String()
}

// requiredLines renders the required-fields guidance for an action schema: the
// root required list, plus per-branch required lists when the contract uses
// oneOf/anyOf/allOf. Branches are never merged — they are presented
// separately.
func requiredLines(raw json.RawMessage) []string {
	var doc struct {
		Required []string          `json:"required"`
		OneOf    []json.RawMessage `json:"oneOf"`
		AnyOf    []json.RawMessage `json:"anyOf"`
		AllOf    []json.RawMessage `json:"allOf"`
	}
	if err := json.Unmarshal(raw, &doc); err != nil {
		return nil
	}
	var lines []string
	if len(doc.Required) > 0 {
		lines = append(lines, "Required: "+strings.Join(doc.Required, ", "))
	}
	for i, br := range doc.OneOf {
		if r := branchRequired(br); len(r) > 0 {
			lines = append(lines, fmt.Sprintf("Required (oneOf branch %d): %s", i+1, strings.Join(r, ", ")))
		}
	}
	for i, br := range doc.AnyOf {
		if r := branchRequired(br); len(r) > 0 {
			lines = append(lines, fmt.Sprintf("Required (anyOf branch %d): %s", i+1, strings.Join(r, ", ")))
		}
	}
	for _, br := range doc.AllOf {
		if r := branchRequired(br); len(r) > 0 {
			lines = append(lines, "Required (allOf): "+strings.Join(r, ", "))
		}
	}
	return lines
}

func branchRequired(branch json.RawMessage) []string {
	var s struct {
		Required []string `json:"required"`
	}
	if err := json.Unmarshal(branch, &s); err != nil {
		return nil
	}
	return s.Required
}
