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

package bound

import (
	"encoding/json"
	"strings"
	"unicode/utf8"
)

const (
	MaxBytes = 60000
	MaxItems = 50

	maxPreviewBytes = 4000
)

const (
	endpointListNote    = "Typed MCP endpoint response was bounded for MCP output. Narrow the request with limit, offset, filters, search/sql, or a command-mirror tool with --agent/--compact/--select."
	endpointPreviewNote = "Typed MCP endpoint response exceeded the tool result budget and was not a recognized list envelope. Narrow the request with filters, search/sql, or a command-mirror tool with --agent/--compact/--select."
	jsonResultNote      = "MCP JSON result exceeded the tool result budget. Narrow the request with limit, filters, search/sql, or --select/--compact where available."
	textResultNote      = "MCP command output exceeded the tool result budget. Rerun with narrower flags, --agent, --compact, --select, or --limit where available."
)

// EndpointResponse renders a typed endpoint response within the MCP result
// budget. GET array responses are always wrapped so agents get count metadata;
// other small responses pass through unchanged.
func EndpointResponse(method string, data json.RawMessage) string {
	trimmed := strings.TrimSpace(string(data))
	if strings.EqualFold(method, "GET") && len(trimmed) > 0 && trimmed[0] == '[' {
		var items []json.RawMessage
		if json.Unmarshal(data, &items) == nil {
			return string(boundedListEnvelope("items", items, len(data), endpointListNote))
		}
	}
	if len(data) <= MaxBytes {
		return string(data)
	}
	if strings.EqualFold(method, "GET") {
		if out, ok := boundedSingleArrayObject(data); ok {
			return string(out)
		}
	}
	return previewEnvelope(data, endpointPreviewNote)
}

// JSON renders an arbitrary JSON value within the MCP result budget. Small
// results pass through unchanged; oversized arrays become bounded list
// envelopes before falling back to a preview envelope.
func JSON(v any) (string, error) {
	data, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return "", err
	}
	if len(data) <= MaxBytes {
		return string(data), nil
	}

	compact, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	if len(compact) <= MaxBytes {
		return string(compact), nil
	}

	trimmed := strings.TrimSpace(string(compact))
	if len(trimmed) > 0 && trimmed[0] == '[' {
		var items []json.RawMessage
		if json.Unmarshal(compact, &items) == nil {
			return string(boundedListEnvelope("items", items, len(compact), jsonResultNote)), nil
		}
	}
	return previewEnvelope(compact, jsonResultNote), nil
}

// Text renders non-JSON command output within the MCP result budget. Small
// output passes through unchanged; oversized output becomes a JSON preview
// envelope so the result stays valid and self-describing.
func Text(out string) string {
	if len(out) <= MaxBytes {
		return out
	}
	return previewEnvelope([]byte(out), textResultNote)
}

func boundedSingleArrayObject(data json.RawMessage) ([]byte, bool) {
	var obj map[string]json.RawMessage
	if json.Unmarshal(data, &obj) != nil {
		return nil, false
	}
	arrayField := ""
	var items []json.RawMessage
	for key, raw := range obj {
		trimmed := strings.TrimSpace(string(raw))
		if len(trimmed) == 0 || trimmed[0] != '[' {
			continue
		}
		var candidate []json.RawMessage
		if json.Unmarshal(raw, &candidate) != nil {
			continue
		}
		if arrayField != "" {
			return nil, false
		}
		arrayField = key
		items = candidate
	}
	if arrayField == "" {
		return nil, false
	}

	build := func(subset []json.RawMessage) any {
		out := make(map[string]any, len(obj)+6)
		for key, raw := range obj {
			if key == arrayField {
				out[key] = subset
				continue
			}
			out[key] = raw
		}
		if len(subset) < len(items) {
			out["_pp_truncated"] = true
			out["_pp_total_count"] = len(items)
			out["_pp_returned_count"] = len(subset)
			out["_pp_original_bytes"] = len(data)
			out["_pp_max_bytes"] = MaxBytes
			out["_pp_note"] = endpointListNote
		}
		return out
	}
	out := fitJSONItems(items, build)
	if len(out) > MaxBytes {
		return nil, false
	}
	return out, true
}

func boundedListEnvelope(field string, items []json.RawMessage, originalBytes int, note string) []byte {
	build := func(subset []json.RawMessage) any {
		out := map[string]any{
			"count": len(items),
			field:   subset,
		}
		if len(subset) < len(items) {
			out["truncated"] = true
			out["returned_count"] = len(subset)
			out["original_bytes"] = originalBytes
			out["max_bytes"] = MaxBytes
			out["note"] = note
		}
		return out
	}
	return fitJSONItems(items, build)
}

func fitJSONItems(items []json.RawMessage, build func([]json.RawMessage) any) []byte {
	limit := len(items)
	if limit > MaxItems {
		limit = MaxItems
	}
	for n := limit; n > 0; n-- {
		out, err := json.Marshal(build(items[:n]))
		if err != nil {
			continue
		}
		if len(out) <= MaxBytes {
			return out
		}
	}
	out, _ := json.Marshal(build(items[:0]))
	return out
}

func previewEnvelope(data []byte, note string) string {
	limit := len(data)
	if limit > maxPreviewBytes {
		limit = maxPreviewBytes
	}
	for limit >= 0 {
		out, err := json.Marshal(map[string]any{
			"truncated":      true,
			"original_bytes": len(data),
			"max_bytes":      MaxBytes,
			"preview":        previewString(data, limit),
			"note":           note,
		})
		if err == nil && len(out) <= MaxBytes {
			return string(out)
		}
		if limit == 0 {
			break
		}
		if limit < 512 {
			limit = 0
		} else {
			limit -= 512
		}
	}
	out, _ := json.Marshal(map[string]any{
		"truncated":      true,
		"original_bytes": len(data),
		"max_bytes":      MaxBytes,
		"note":           note,
	})
	return string(out)
}

func previewString(data []byte, limit int) string {
	if limit > len(data) {
		limit = len(data)
	}
	for limit > 0 {
		r, size := utf8.DecodeLastRune(data[:limit])
		if r != utf8.RuneError || size != 1 {
			break
		}
		limit--
	}
	return string(data[:limit])
}
