// Copyright 2026 printing-press-golden and contributors. Licensed under Apache-2.0. See LICENSE.
// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.

// Package mcp — code-orchestration thin surface.
//
// Two tools cover the entire API: <api>_search to discover endpoints, and
// <api>_execute to invoke one. This collapses a large API (50+ endpoints)
// to ~1K tokens of tool definitions while preserving full coverage — the
// agent writes the composition logic in its own sandbox.
//
// Pattern source: Anthropic 2026-04-22 "Building agents that reach
// production systems with MCP" — Cloudflare's MCP server covers ~2,500
// endpoints in roughly 1K tokens via the same search+execute shape.

package mcp

import (
	"context"
	"encoding/json"
	"fmt"
	neturl "net/url"
	"sort"
	"strings"

	mcplib "github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"mcp-cloudflare-pp-cli/internal/mcp/bound"
)

// RegisterCodeOrchestrationTools registers the two agent-facing tools that
// cover the whole API surface. Called from RegisterTools in place of the
// per-endpoint registrations when MCP.Orchestration is "code".
func RegisterCodeOrchestrationTools(s *server.MCPServer) {
	s.AddTool(
		mcplib.NewTool("mcp-cloudflare_search",
			mcplib.WithDescription("Search the mcp-cloudflare API for endpoints matching a natural-language query. Returns a ranked list of {endpoint_id, method, path, summary} entries. Call this first to find the endpoint to execute."),
			mcplib.WithString("query", mcplib.Required(), mcplib.Description("Natural-language description of what you want to do.")),
			mcplib.WithNumber("limit", mcplib.Description("Max endpoints to return (default 10).")),
		),
		handleCodeOrchSearch,
	)

	s.AddTool(
		mcplib.NewTool("mcp-cloudflare_execute",
			mcplib.WithDescription("Execute one mcp-cloudflare API endpoint by its endpoint_id (from mcp-cloudflare_search). Params are passed as a JSON object; path placeholders and query strings are resolved automatically."),
			mcplib.WithString("endpoint_id", mcplib.Required(), mcplib.Description("Endpoint identifier returned by mcp-cloudflare_search (e.g., \"users.list\").")),
			mcplib.WithObject("params", mcplib.Description("Parameters for the endpoint. Path placeholders match by name; remaining entries become query string on GET/DELETE or JSON body on POST/PUT/PATCH.")),
		),
		handleCodeOrchExecute,
	)
}

// codeOrchEndpoint captures the small slice of endpoint metadata the
// search+execute pair needs at runtime. `keywords` is a precomputed
// lowercase stream of description + path tokens used for naive ranking;
// anything more sophisticated belongs on the agent side.
type codeOrchEndpoint struct {
	ID         string
	Method     string
	Path       string
	Tier       string
	Summary    string
	Positional []string
	// TemplateParams carries public-to-wire bindings for promoted global path
	// placeholders. These resolve through Config.TemplateVars just like the
	// per-endpoint MCP tool inputs do.
	TemplateParams []codeOrchParamBinding
	// QueryParams carries public-to-wire bindings for spec-declared in:query
	// parameters. Write methods (POST/PUT/PATCH) route these to the query
	// string instead of dumping them into the JSON body. Derived from the
	// same mcpParamBindings location data the per-endpoint tools use.
	QueryParams []codeOrchParamBinding
	// HeaderOverrides carries per-endpoint request headers (e.g. an
	// Accept override for binary-only response endpoints). Without
	// threading these through, the code-orchestration execute path
	// sends the client's default Accept and binary endpoints 406.
	HeaderOverrides map[string]string
	// BodyIsArray marks endpoints whose request body schema root is a
	// bare top-level JSON array. The execute path then sends a top-level
	// array (the agent supplies it as params["body"]) instead of the
	// params object; a strict-mapping API rejects an object at the body
	// root with HTTP 422 "Invalid json".
	BodyIsArray bool
	keywords    []string
}

type codeOrchParamBinding struct {
	PublicName string
	WireName   string
}

// codeOrchEndpoints is the generator-populated registry covering every
// endpoint declared in the spec. Kept flat on purpose — the agent queries
// via <api>_search, so hierarchy shows up as dotted IDs, not nested maps.
var codeOrchEndpoints = []codeOrchEndpoint{
	{
		ID:             "items.list",
		Method:         "GET",
		Path:           "/items",
		Summary:        "List items",
		Positional:     []string{},
		TemplateParams: []codeOrchParamBinding{},
		QueryParams:    []codeOrchParamBinding{},
		keywords:       codeOrchKeywords("items", "list", "List items", "/items"),
	},
}

// codeOrchStopwords filters two-letter and short common-word substrings
// that pollute ranking via the substring-contains rule. Without them, a
// search for "list links" matches every endpoint whose description
// contains "is enrolled" because "is" is two chars and the matcher
// accepts kw.contains(t) || t.contains(kw).
var codeOrchStopwords = map[string]bool{
	"a": true, "an": true, "and": true, "are": true, "as": true,
	"at": true, "be": true, "by": true, "for": true, "from": true,
	"has": true, "in": true, "is": true, "it": true, "its": true,
	"of": true, "on": true, "or": true, "that": true, "the": true,
	"this": true, "to": true, "was": true, "will": true, "with": true,
	"your": true, "you": true, "any": true, "all": true,
}

// codeOrchKeywords produces the lowercase token stream used for search
// ranking. Defined at package level so the registry initializer can call it
// inline above without pulling in a separate precompute step.
func codeOrchKeywords(resource, endpoint, summary, path string) []string {
	raw := strings.ToLower(resource + " " + endpoint + " " + summary + " " + path)
	raw = strings.Map(func(r rune) rune {
		switch r {
		case '_', '-', '/', '{', '}', '.', ',', ':', ';':
			return ' '
		}
		return r
	}, raw)
	out := make([]string, 0, 16)
	seen := map[string]bool{}
	for _, tok := range strings.Fields(raw) {
		if len(tok) < 3 || codeOrchStopwords[tok] || seen[tok] {
			continue
		}
		seen[tok] = true
		out = append(out, tok)
	}
	return out
}

func handleCodeOrchSearch(ctx context.Context, req mcplib.CallToolRequest) (*mcplib.CallToolResult, error) {
	args := req.GetArguments()
	query, ok := args["query"].(string)
	if !ok || strings.TrimSpace(query) == "" {
		return mcplib.NewToolResultError("query is required"), nil
	}
	limit := 10
	if v, ok := args["limit"].(float64); ok && v > 0 {
		limit = int(v)
	}

	terms := codeOrchKeywords("", "", query, "")
	type scored struct {
		ep    *codeOrchEndpoint
		score int
	}
	results := make([]scored, 0, len(codeOrchEndpoints))
	for i := range codeOrchEndpoints {
		ep := &codeOrchEndpoints[i]
		score := 0
		for _, t := range terms {
			for _, kw := range ep.keywords {
				if kw == t {
					score += 2
				} else if strings.Contains(kw, t) || strings.Contains(t, kw) {
					score++
				}
			}
		}
		if score > 0 {
			results = append(results, scored{ep: ep, score: score})
		}
	}
	sort.SliceStable(results, func(i, j int) bool { return results[i].score > results[j].score })
	if len(results) > limit {
		results = results[:limit]
	}

	out := make([]map[string]any, 0, len(results))
	for _, r := range results {
		out = append(out, map[string]any{
			"endpoint_id": r.ep.ID,
			"method":      r.ep.Method,
			"path":        r.ep.Path,
			"summary":     r.ep.Summary,
			"score":       r.score,
		})
	}
	text, err := bound.JSON(map[string]any{"count": len(out), "results": out})
	if err != nil {
		return mcplib.NewToolResultError(fmt.Sprintf("encoding search results: %v", err)), nil
	}
	return mcplib.NewToolResultText(text), nil
}

func handleCodeOrchExecute(ctx context.Context, req mcplib.CallToolRequest) (*mcplib.CallToolResult, error) {
	args := req.GetArguments()
	id, ok := args["endpoint_id"].(string)
	if !ok || id == "" {
		return mcplib.NewToolResultError("endpoint_id is required (call mcp-cloudflare_search first)"), nil
	}

	var ep *codeOrchEndpoint
	for i := range codeOrchEndpoints {
		if codeOrchEndpoints[i].ID == id {
			ep = &codeOrchEndpoints[i]
			break
		}
	}
	if ep == nil {
		return mcplib.NewToolResultError(fmt.Sprintf("unknown endpoint_id %q — call mcp-cloudflare_search to discover valid ids", id)), nil
	}

	params, _ := args["params"].(map[string]any)
	if params == nil {
		params = map[string]any{}
	}

	c, err := newMCPClient()
	if err != nil {
		return mcplib.NewToolResultError(err.Error()), nil
	}

	path := ep.Path
	for _, p := range ep.Positional {
		if v, ok := params[p]; ok {
			path = strings.ReplaceAll(path, "{"+p+"}", formatMCPParamValue(v))
			delete(params, p)
		}
	}

	// Route params to their runtime slots. GET/DELETE params are query
	// strings; write methods split spec-declared query params from the
	// remaining params used as the request body below.
	query := map[string]string{}
	if ep.Method == "GET" || ep.Method == "DELETE" {
		for k, v := range params {
			query[codeOrchWireQueryName(ep.QueryParams, k)] = formatMCPParamValue(v)
		}
	} else {
		// Route spec-declared in:query params to the query string for write
		// methods too. Without this, a query param (e.g. sendToLedger on
		// PUT /ledger/voucher/{id}) wrongly lands in the JSON body and the
		// API silently ignores it or rejects the request. The remaining
		// params stay in the map for codeOrchWriteBody (the JSON body).
		if enc := codeOrchSplitQuery(ep.QueryParams, params); enc != "" {
			sep := "?"
			if strings.Contains(path, "?") {
				sep = "&"
			}
			path += sep + enc
		}
	}

	hdrs := ep.HeaderOverrides
	writeBody := func() any {
		if ep.BodyIsArray {
			return codeOrchArrayBody(params)
		}
		return codeOrchWriteBody(params)
	}
	var data json.RawMessage
	switch ep.Method {
	case "GET":
		if len(hdrs) > 0 {
			data, err = c.GetWithHeaders(ctx, path, query, hdrs)
		} else {
			data, err = c.Get(ctx, path, query)
		}
	case "DELETE":
		if len(hdrs) > 0 {
			data, _, err = c.DeleteWithParamsAndHeaders(ctx, path, query, hdrs)
		} else {
			data, _, err = c.DeleteWithParams(ctx, path, query)
		}
	case "POST":
		body := writeBody()
		if len(hdrs) > 0 {
			data, _, err = c.PostWithHeaders(ctx, path, body, hdrs)
		} else {
			data, _, err = c.Post(ctx, path, body)
		}
	case "PUT":
		body := writeBody()
		if len(hdrs) > 0 {
			data, _, err = c.PutWithHeaders(ctx, path, body, hdrs)
		} else {
			data, _, err = c.Put(ctx, path, body)
		}
	case "PATCH":
		body := writeBody()
		if len(hdrs) > 0 {
			data, _, err = c.PatchWithHeaders(ctx, path, body, hdrs)
		} else {
			data, _, err = c.Patch(ctx, path, body)
		}
	default:
		return mcplib.NewToolResultError(fmt.Sprintf("unsupported method %q", ep.Method)), nil
	}
	if err != nil {
		return mcplib.NewToolResultError(err.Error()), nil
	}
	return mcplib.NewToolResultText(bound.EndpointResponse(ep.Method, data)), nil
}

// codeOrchWriteBody returns the value handed to the client layer as the
// request body for write methods (POST/PUT/PATCH). It MUST be the structured
// params map, never pre-marshaled bytes.
//
// client.do() marshals the body value exactly once. Handing it []byte makes
// json.Marshal([]byte) emit a base64-encoded JSON *string*, so the API
// receives "eyJ...==" where it expects the request object. Strict JSON APIs
// reject that as the wrong type at the body root. GET/DELETE carry no body,
// so this defect stays latent until the first write attempt.
func codeOrchWriteBody(params map[string]any) any {
	return params
}

// codeOrchArrayBody returns the request body for endpoints whose schema root
// is a bare top-level JSON array (ep.BodyIsArray). Such a body cannot be
// expressed as the params object, so the agent supplies the array under the
// conventional params key "body" (these endpoints have no flattened named
// params, so there is no collision risk). When present and array-shaped it is
// sent as the top-level array the API expects. Otherwise params is returned
// unchanged so a malformed call fails loudly at the API (HTTP 422) instead of
// silently sending the wrong shape or a partial write — financial mutations
// are never retried with body-variant guesses (broken-convenience guardrail).
func codeOrchArrayBody(params map[string]any) any {
	if v, ok := params["body"]; ok {
		if arr, ok := v.([]any); ok {
			return arr
		}
	}
	return params
}

// codeOrchSplitQuery removes spec-declared in:query params from params and
// returns them URL-encoded for appending to the request path. The remaining
// entries stay in the map for codeOrchWriteBody (the JSON body), so a write
// method's query parameters never get buried in the body. Mutates params by
// design (deletes the consumed query keys).
func codeOrchSplitQuery(queryParams []codeOrchParamBinding, params map[string]any) string {
	uv := neturl.Values{}
	for _, q := range queryParams {
		for _, key := range []string{q.PublicName, q.WireName} {
			if key == "" {
				continue
			}
			if v, ok := params[key]; ok {
				uv.Set(q.WireName, formatMCPParamValue(v))
				delete(params, key)
				break
			}
		}
	}
	return uv.Encode()
}

func codeOrchWireQueryName(queryParams []codeOrchParamBinding, name string) string {
	for _, q := range queryParams {
		if q.PublicName == name || q.WireName == name {
			return q.WireName
		}
	}
	return name
}
