package openai

import (
	"bytes"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"sync"

	edgeservice "iop/apps/edge/internal/service"
	"iop/packages/go/config"
)

const (
	maxHotPathSelectorProviderInstructionBytes = 4096
	maxHotPathSelectorOutputTokens             = 4096
	hotPathArtifactPairToolName                = "iop_write_artifact_pair"
)

func buildHotPathSelectorProviderInstruction(requestID string, state selectorInstructionState) (string, error) {
	if !validLogicalRequestID(requestID) {
		return "", fmt.Errorf("selector artifact request identity is invalid")
	}
	paths := newReservedPaths(requestID)
	var instruction string
	switch state {
	case selectorInstructionPrepareOnly:
		instruction = fmt.Sprintf(`IOP caller-workspace selector instruction.
Operation: prepare-only
Return exactly one admitted prepare tool call for this request-local job directory and no other tool call:
JOB directory: %s
Do not write PLAN or REVIEW in this turn. Do not mention or infer an absolute workspace path.`, paths.JobDir)
	case selectorInstructionPairWrite:
		instruction = fmt.Sprintf(`IOP caller-workspace selector instruction.
Operation: pair-write
Return exactly one iop_write_artifact_pair tool call containing both complete artifact contents and no other tool call. Do not mention or infer an absolute workspace path.
PLAN path: %s
REVIEW path: %s
The default PLAN grammar accepts 2-6 consecutive steps and 1-3 verification bullets. For this deterministic seed, use exactly the following two-step, one-verification form and replace only angle-bracketed text:
Write every PLAN field in English using ASCII characters only.
# Plan

## Goal
<one non-empty line>

## Steps
- [P1] <non-empty one-line step>
- [P2] <non-empty one-line step>

## Verification
- <non-empty one-line verification>
The pending REVIEW content must be exactly this deterministic seed:
# Review

## Worker Item Status
- P1: pending
- P2: pending

## Worker Changes
Pending worker execution.

## Worker Verification
Pending worker verification.

## Deviations
None recorded.`, paths.PlanPath, paths.ReviewPath)
	default:
		return "", fmt.Errorf("selector provider instruction state is invalid")
	}
	if len(instruction) > maxHotPathSelectorProviderInstructionBytes {
		return "", fmt.Errorf("selector provider instruction exceeds bounded size")
	}
	return instruction, nil
}

func prepareHotPathSelectorProviderInstruction(tunnel edgeservice.SubmitProviderTunnelRequest, instruction string) (edgeservice.SubmitProviderTunnelRequest, error) {
	if strings.TrimSpace(instruction) == "" {
		return tunnel, nil
	}
	rewrite := func(body []byte) ([]byte, error) {
		switch config.ProtocolOperation(tunnel.Operation) {
		case config.OperationChatCompletions:
			return injectHotPathChatSelectorInstruction(body, instruction)
		case config.OperationMessages:
			return injectHotPathAnthropicSelectorInstruction(body, instruction)
		default:
			return nil, fmt.Errorf("selector provider instruction does not support operation %q", tunnel.Operation)
		}
	}
	if tunnel.BuildBody != nil {
		build := tunnel.BuildBody
		tunnel.BuildBody = func(target string) ([]byte, error) {
			body, err := build(target)
			if err != nil {
				return nil, err
			}
			return rewrite(body)
		}
		return tunnel, nil
	}
	if len(tunnel.Body) == 0 {
		return tunnel, fmt.Errorf("selector provider body is unavailable")
	}
	body, err := rewrite(tunnel.Body)
	if err != nil {
		return tunnel, err
	}
	tunnel.Body = body
	return tunnel, nil
}

func prepareHotPathSelectorOutputLimit(tunnel edgeservice.SubmitProviderTunnelRequest) (edgeservice.SubmitProviderTunnelRequest, error) {
	rewrite := func(body []byte) ([]byte, error) {
		limitJSON, err := json.Marshal(maxHotPathSelectorOutputTokens)
		if err != nil {
			return nil, err
		}
		plan, err := planTopLevelJSONPatches(body, []topLevelJSONPatch{
			{name: "max_tokens", value: limitJSON},
			{name: "max_completion_tokens"},
		})
		if err != nil {
			return nil, err
		}
		return plan.apply(), nil
	}
	if tunnel.BuildBody != nil {
		build := tunnel.BuildBody
		tunnel.BuildBody = func(target string) ([]byte, error) {
			body, err := build(target)
			if err != nil {
				return nil, err
			}
			return rewrite(body)
		}
		return tunnel, nil
	}
	if len(tunnel.Body) == 0 {
		return tunnel, fmt.Errorf("selector provider body is unavailable")
	}
	body, err := rewrite(tunnel.Body)
	if err != nil {
		return tunnel, err
	}
	tunnel.Body = body
	return tunnel, nil
}

func prepareHotPathSelectorCanonicalTools(tunnel edgeservice.SubmitProviderTunnelRequest, instruction string, preset config.ExecutionPreset) (edgeservice.SubmitProviderTunnelRequest, error) {
	operation := ""
	switch {
	case strings.Contains(instruction, "Operation: prepare-only"):
		operation = "prepare"
	case strings.Contains(instruction, "Operation: pair-write"):
		operation = "write"
	default:
		return tunnel, fmt.Errorf("selector canonical tool operation is unavailable")
	}
	toolNames := make(map[string]struct{})
	for _, alternative := range preset.WorkspaceTools {
		configured, ok := alternative.Operations[operation]
		if ok && strings.TrimSpace(configured.ToolName) != "" {
			toolNames[strings.TrimSpace(configured.ToolName)] = struct{}{}
		}
	}
	if len(toolNames) == 0 {
		return tunnel, fmt.Errorf("selector canonical tool binding is unavailable")
	}
	rewrite := func(body []byte) ([]byte, error) {
		var root map[string]any
		if err := json.Unmarshal(body, &root); err != nil {
			return nil, fmt.Errorf("decode selector provider tools: %w", err)
		}
		actual := make(map[string]map[string]any)
		for _, raw := range anySlice(root["tools"]) {
			tool, _ := raw.(map[string]any)
			function, _ := tool["function"].(map[string]any)
			name, _ := function["name"].(string)
			if _, ok := toolNames[strings.TrimSpace(name)]; ok {
				actual[strings.TrimSpace(name)] = function
			}
		}
		if len(actual) == 0 {
			return nil, fmt.Errorf("selector canonical tool is absent from caller tools")
		}
		if operation == "write" {
			root["tools"] = []any{map[string]any{
				"type": "function",
				"function": map[string]any{
					"name":        hotPathArtifactPairToolName,
					"description": "Author the complete IOP Plan and pending Review artifact pair in one atomic selector decision.",
					"parameters": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"plan_content":   map[string]any{"type": "string", "description": "Complete Plan markdown"},
							"review_content": map[string]any{"type": "string", "description": "Complete pending Review markdown"},
						},
						"required":             []any{"plan_content", "review_content"},
						"additionalProperties": false,
					},
				},
			}}
			root["tool_choice"] = "required"
			delete(root, "parallel_tool_calls")
			return json.Marshal(root)
		}
		names := make([]string, 0, len(actual))
		for name := range actual {
			names = append(names, name)
		}
		sort.Strings(names)
		canonical := make([]any, 0, len(actual))
		for _, name := range names {
			function := actual[name]
			properties := map[string]any{
				"path": map[string]any{"type": "string", "description": "IOP-issued relative workspace path"},
			}
			required := []any{"path"}
			if operation == "write" {
				properties["content"] = map[string]any{"type": "string", "description": "Complete file content"}
				required = append(required, "content")
			}
			description, _ := function["description"].(string)
			canonical = append(canonical, map[string]any{
				"type": "function",
				"function": map[string]any{
					"name": name, "description": strings.TrimSpace(description + " IOP canonical " + operation + " operation."),
					"parameters": map[string]any{"type": "object", "properties": properties, "required": required, "additionalProperties": false},
				},
			})
		}
		root["tools"] = canonical
		root["tool_choice"] = "required"
		delete(root, "parallel_tool_calls")
		return json.Marshal(root)
	}
	if tunnel.BuildBody == nil {
		return tunnel, fmt.Errorf("selector provider body builder is unavailable")
	}
	build := tunnel.BuildBody
	tunnel.BuildBody = func(target string) ([]byte, error) {
		body, err := build(target)
		if err != nil {
			return nil, err
		}
		return rewrite(body)
	}
	return tunnel, nil
}

func injectHotPathChatSelectorInstruction(body []byte, instruction string) ([]byte, error) {
	var envelope struct {
		Messages []json.RawMessage `json:"messages"`
	}
	if err := json.Unmarshal(body, &envelope); err != nil {
		return nil, fmt.Errorf("decode selector Chat provider body: %w", err)
	}
	if len(envelope.Messages) == 0 {
		return nil, fmt.Errorf("selector Chat provider body has no messages")
	}
	insertAt := 0
	for insertAt < len(envelope.Messages) {
		var message struct {
			Role string `json:"role"`
		}
		if err := json.Unmarshal(envelope.Messages[insertAt], &message); err != nil || message.Role != "system" {
			break
		}
		insertAt++
	}
	injected, err := json.Marshal(map[string]string{"role": "system", "content": instruction})
	if err != nil {
		return nil, err
	}
	messages := make([]json.RawMessage, 0, len(envelope.Messages)+1)
	messages = append(messages, envelope.Messages[:insertAt]...)
	messages = append(messages, injected)
	messages = append(messages, envelope.Messages[insertAt:]...)
	messagesJSON, err := json.Marshal(messages)
	if err != nil {
		return nil, err
	}
	plan, err := planTopLevelJSONPatches(body, []topLevelJSONPatch{{name: "messages", value: messagesJSON}})
	if err != nil {
		return nil, err
	}
	return plan.apply(), nil
}

func injectHotPathAnthropicSelectorInstruction(body []byte, instruction string) ([]byte, error) {
	fields, _, err := scanTopLevelJSONObject(body)
	if err != nil {
		return nil, fmt.Errorf("decode selector Messages provider body: %w", err)
	}
	blocks := make([]json.RawMessage, 0, 2)
	for _, field := range fields {
		if field.name != "system" {
			continue
		}
		raw := bytes.TrimSpace(body[field.valueFrom:field.valueTo])
		if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
			break
		}
		if raw[0] == '"' {
			var text string
			if err := json.Unmarshal(raw, &text); err != nil {
				return nil, fmt.Errorf("decode selector Messages system text: %w", err)
			}
			block, err := json.Marshal(map[string]string{"type": "text", "text": text})
			if err != nil {
				return nil, err
			}
			blocks = append(blocks, block)
			break
		}
		if err := json.Unmarshal(raw, &blocks); err != nil {
			return nil, fmt.Errorf("decode selector Messages system blocks: %w", err)
		}
		break
	}
	injected, err := json.Marshal(map[string]string{"type": "text", "text": instruction})
	if err != nil {
		return nil, err
	}
	blocks = append(blocks, injected)
	systemJSON, err := json.Marshal(blocks)
	if err != nil {
		return nil, err
	}
	plan, err := planTopLevelJSONPatches(body, []topLevelJSONPatch{{name: "system", value: systemJSON}})
	if err != nil {
		return nil, err
	}
	return plan.apply(), nil
}

type openAIProviderBodyBuilder struct {
	mu     sync.Mutex
	build  func(string) (*openAIRebuiltLease, error)
	lease  *openAIRebuiltLease
	built  bool
	closed bool
}

func newOpenAIProviderBodyBuilder(build func(string) (*openAIRebuiltLease, error)) *openAIProviderBodyBuilder {
	return &openAIProviderBodyBuilder{build: build}
}

func (b *openAIProviderBodyBuilder) BuildBody(target string) ([]byte, error) {
	if b == nil {
		return nil, fmt.Errorf("OpenAI provider body builder is unavailable")
	}
	b.mu.Lock()
	if b.closed || b.built || b.build == nil {
		b.mu.Unlock()
		return nil, fmt.Errorf("OpenAI provider body builder is unavailable")
	}
	b.built = true
	build := b.build
	b.mu.Unlock()

	lease, err := build(target)
	if err != nil {
		return nil, err
	}
	b.mu.Lock()
	if b.closed {
		b.mu.Unlock()
		lease.release()
		return nil, fmt.Errorf("OpenAI provider body builder is unavailable")
	}
	b.lease = lease
	b.mu.Unlock()
	body, err := lease.body()
	if err != nil {
		b.Close()
		return nil, err
	}
	return body, nil
}

func (b *openAIProviderBodyBuilder) Close() {
	if b == nil {
		return
	}
	b.mu.Lock()
	b.closed = true
	b.build = nil
	lease := b.lease
	b.lease = nil
	b.mu.Unlock()
	lease.release()
}

type providerModelRewriter struct {
	streaming    bool
	model        string
	toolCallWire string
	pending      []byte
}

func newProviderModelRewriter(streaming bool, model string) *providerModelRewriter {
	return newProviderModelRewriterWithToolCallWire(streaming, model, "")
}

func newProviderModelRewriterForDispatch(streaming bool, model string, dispatch edgeservice.RunDispatch) *providerModelRewriter {
	return newProviderModelRewriterWithToolCallWire(streaming, model, dispatch.ProfileToolCallWire)
}

func newProviderModelRewriterWithToolCallWire(streaming bool, model, toolCallWire string) *providerModelRewriter {
	model = strings.TrimSpace(model)
	toolCallWire = strings.TrimSpace(toolCallWire)
	if model == "" && toolCallWire == "" {
		return nil
	}
	return &providerModelRewriter{streaming: streaming, model: model, toolCallWire: toolCallWire}
}

func (r *providerModelRewriter) setToolCallWire(toolCallWire string) {
	if r != nil {
		r.toolCallWire = strings.TrimSpace(toolCallWire)
	}
}

func (r *providerModelRewriter) AppendStream(chunk []byte) []byte {
	if r == nil || len(chunk) == 0 {
		return chunk
	}
	if !r.streaming {
		r.pending = append(r.pending, chunk...)
		return nil
	}
	r.pending = append(r.pending, chunk...)
	var out bytes.Buffer
	for {
		idx := bytes.IndexByte(r.pending, '\n')
		if idx < 0 {
			break
		}
		line := r.pending[:idx+1]
		out.Write(rewriteProviderSSELine(line, r.model, r.toolCallWire))
		r.pending = r.pending[idx+1:]
	}
	return out.Bytes()
}

func (r *providerModelRewriter) FlushStream() []byte {
	if r == nil || len(r.pending) == 0 {
		return nil
	}
	pending := r.pending
	r.pending = nil
	if !r.streaming {
		return r.RewriteComplete(pending)
	}
	return rewriteProviderSSELine(pending, r.model, r.toolCallWire)
}

func (r *providerModelRewriter) RewriteComplete(body []byte) []byte {
	if r == nil || len(body) == 0 {
		return body
	}
	return rewriteProviderJSONResponse(body, r.model, r.toolCallWire)
}

func rewriteProviderSSEModelLine(line []byte, model string) []byte {
	return rewriteProviderSSELine(line, model, "")
}

func rewriteProviderSSELine(line []byte, model, toolCallWire string) []byte {
	body, ending := splitLineEnding(line)
	prefix, payload, ok := bytes.Cut(body, []byte(":"))
	if !ok || strings.TrimSpace(string(prefix)) != "data" {
		return line
	}
	payload = bytes.TrimSpace(payload)
	if len(payload) == 0 || bytes.Equal(payload, []byte("[DONE]")) {
		return line
	}
	rewritten := rewriteProviderJSONResponse(payload, model, toolCallWire)
	if bytes.Equal(rewritten, payload) {
		return line
	}
	out := make([]byte, 0, len("data: ")+len(rewritten)+len(ending))
	out = append(out, "data: "...)
	out = append(out, rewritten...)
	out = append(out, ending...)
	return out
}

func rewriteProviderJSONResponse(body []byte, model, toolCallWire string) []byte {
	rewritten := rewriteProviderJSONModel(body, model)
	if toolCallWire != config.ProtocolToolCallWireGeminiChat {
		return rewritten
	}
	return normalizeGeminiChatProviderResponse(rewritten)
}

func normalizeGeminiChatProviderResponse(body []byte) []byte {
	if !bytes.Contains(body, []byte(`"thought_signature"`)) {
		return body
	}
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.UseNumber()
	var response map[string]any
	if err := decoder.Decode(&response); err != nil {
		return body
	}
	changed := false
	for _, rawChoice := range anySlice(response["choices"]) {
		choice, ok := rawChoice.(map[string]any)
		if !ok {
			continue
		}
		for _, messageKey := range []string{"message", "delta"} {
			message, ok := choice[messageKey].(map[string]any)
			if !ok {
				continue
			}
			for _, rawCall := range anySlice(message["tool_calls"]) {
				call, ok := rawCall.(map[string]any)
				if !ok {
					continue
				}
				id, idOK := call["id"].(string)
				extra, extraOK := call["extra_content"].(map[string]any)
				if !idOK || id == "" || !extraOK {
					continue
				}
				google, googleOK := extra["google"].(map[string]any)
				if !googleOK {
					continue
				}
				signature, signatureOK := google["thought_signature"].(string)
				if !signatureOK || signature == "" {
					continue
				}
				call["id"] = encodeGeminiThoughtSignatureToolID(id, signature)
				delete(google, "thought_signature")
				if len(google) == 0 {
					delete(extra, "google")
				}
				if len(extra) == 0 {
					delete(call, "extra_content")
				}
				changed = true
			}
		}
	}
	if !changed {
		return body
	}
	encoded, err := json.Marshal(response)
	if err != nil {
		return body
	}
	return encoded
}

func normalizeGeminiChatProviderRequest(body []byte) ([]byte, error) {
	if !bytes.Contains(body, []byte(geminiThoughtSignatureToolIDPrefix)) {
		return body, nil
	}
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.UseNumber()
	var request map[string]any
	if err := decoder.Decode(&request); err != nil {
		return nil, fmt.Errorf("decode Gemini Chat provider request: %w", err)
	}
	changed := false
	for _, rawMessage := range anySlice(request["messages"]) {
		message, ok := rawMessage.(map[string]any)
		if !ok {
			continue
		}
		for _, rawCall := range anySlice(message["tool_calls"]) {
			call, ok := rawCall.(map[string]any)
			if !ok {
				continue
			}
			encodedID, ok := call["id"].(string)
			if !ok {
				continue
			}
			id, signature, encoded, err := decodeGeminiThoughtSignatureToolID(encodedID)
			if err != nil {
				return nil, err
			}
			if !encoded {
				continue
			}
			if err := restoreGeminiThoughtSignature(call, signature); err != nil {
				return nil, err
			}
			call["id"] = id
			changed = true
		}
		if encodedID, ok := message["tool_call_id"].(string); ok {
			id, _, encoded, err := decodeGeminiThoughtSignatureToolID(encodedID)
			if err != nil {
				return nil, err
			}
			if encoded {
				message["tool_call_id"] = id
				changed = true
			}
		}
	}
	if !changed {
		return body, nil
	}
	encoded, err := json.Marshal(request)
	if err != nil {
		return nil, fmt.Errorf("encode Gemini Chat provider request: %w", err)
	}
	return encoded, nil
}

func restoreGeminiThoughtSignature(call map[string]any, signature string) error {
	extra, ok := call["extra_content"].(map[string]any)
	if !ok {
		if call["extra_content"] != nil {
			return fmt.Errorf("restore Gemini thought signature: extra_content is not an object")
		}
		extra = make(map[string]any)
		call["extra_content"] = extra
	}
	google, ok := extra["google"].(map[string]any)
	if !ok {
		if extra["google"] != nil {
			return fmt.Errorf("restore Gemini thought signature: extra_content.google is not an object")
		}
		google = make(map[string]any)
		extra["google"] = google
	}
	if existing, exists := google["thought_signature"]; exists && existing != signature {
		return fmt.Errorf("restore Gemini thought signature: conflicting signature")
	}
	google["thought_signature"] = signature
	return nil
}

func prepareProviderChatToolCallNormalization(tunnel edgeservice.SubmitProviderTunnelRequest, selected edgeservice.ProviderPoolCandidate) (edgeservice.SubmitProviderTunnelRequest, error) {
	if selected.ProtocolProfile == nil {
		return tunnel, nil
	}
	mapping, ok := selected.ProtocolProfile.ToolCallMapping(config.OperationChatCompletions)
	if !ok || mapping.Wire != config.ProtocolToolCallWireGeminiChat {
		return tunnel, nil
	}
	build := tunnel.BuildBody
	if build == nil {
		return tunnel, fmt.Errorf("Gemini Chat provider body builder is unavailable")
	}
	tunnel.BuildBody = func(target string) ([]byte, error) {
		body, err := build(target)
		if err != nil {
			return nil, err
		}
		return normalizeGeminiChatProviderRequest(body)
	}
	return tunnel, nil
}

// prepareProviderChatRequestNormalization applies the bounded Chat field
// aliases owned by the selected protocol profile. A generic OpenAI-compatible
// caller can legally send either token-limit spelling, but current OpenAI Chat
// profiles require max_completion_tokens while Gemini and legacy Chat wires
// use max_tokens. Unknown fields and their original byte ranges remain
// untouched.
func prepareProviderChatRequestNormalization(tunnel edgeservice.SubmitProviderTunnelRequest, selected edgeservice.ProviderPoolCandidate) (edgeservice.SubmitProviderTunnelRequest, error) {
	profile := selected.ProtocolProfile
	if profile == nil || tunnel.Operation != string(config.OperationChatCompletions) {
		return tunnel, nil
	}
	mapping, ok := profile.EffortMapping(config.OperationChatCompletions)
	if !ok {
		return tunnel, nil
	}
	var targetField string
	switch mapping.Wire {
	case config.ProtocolEffortWireOpenAIChat:
		targetField = "max_completion_tokens"
	case config.ProtocolEffortWireGeminiChat:
		targetField = "max_tokens"
	default:
		return tunnel, nil
	}
	rewrite := func(body []byte) ([]byte, error) {
		return normalizeChatTokenLimitField(body, targetField)
	}
	if tunnel.BuildBody != nil {
		build := tunnel.BuildBody
		tunnel.BuildBody = func(target string) ([]byte, error) {
			body, err := build(target)
			if err != nil {
				return nil, err
			}
			return rewrite(body)
		}
		return tunnel, nil
	}
	if len(tunnel.Body) == 0 {
		return tunnel, nil
	}
	body, err := rewrite(tunnel.Body)
	if err != nil {
		return tunnel, err
	}
	tunnel.Body = body
	return tunnel, nil
}

// prepareHotPathChatProviderOperation lets an internal caller-workspace stage
// use the closest provider wire without changing the caller-facing Chat
// surface. In particular, OpenAI Chat cannot combine reasoning effort with
// function tools, while the same profile's Responses operation can. The
// internal stage result is already decoded into the common hot-path shape, so
// this operation switch remains private to IOP.
func prepareHotPathChatProviderOperation(
	tunnel edgeservice.SubmitProviderTunnelRequest,
	selected edgeservice.ProviderPoolCandidate,
	requirements providerRequestRequirements,
) (edgeservice.SubmitProviderTunnelRequest, error) {
	if selected.ProtocolProfile == nil || tunnel.BuildBody == nil {
		return tunnel, errProviderStageMissingBinding
	}
	prepared, err := singleRequestProviderTunnelPreparer(requirements, tunnel.BuildBody)(tunnel, selected)
	if err != nil {
		return tunnel, err
	}
	if prepared.Operation != string(config.OperationChatCompletions) {
		return prepared, nil
	}
	prepared, err = prepareProviderChatRequestNormalization(prepared, selected)
	if err != nil {
		return tunnel, err
	}
	return prepareProviderChatToolCallNormalization(prepared, selected)
}

func chatProviderRequirements(req chatCompletionRequest) providerRequestRequirements {
	requirements := providerRequestRequirements{
		HasTools:         len(req.Tools) > 0,
		Stream:           req.Stream,
		StructuredOutput: req.ResponseFormat != nil,
	}
	if req.ReasoningEffort != nil {
		requirements.Effort = strings.TrimSpace(*req.ReasoningEffort)
	}
	return requirements
}

func normalizeChatTokenLimitField(body []byte, targetField string) ([]byte, error) {
	var limits struct {
		MaxTokens           json.RawMessage `json:"max_tokens"`
		MaxCompletionTokens json.RawMessage `json:"max_completion_tokens"`
	}
	if err := json.Unmarshal(body, &limits); err != nil {
		return nil, fmt.Errorf("decode Chat token limit fields: %w", err)
	}
	var value json.RawMessage
	var remove string
	switch targetField {
	case "max_completion_tokens":
		value, remove = limits.MaxCompletionTokens, "max_tokens"
		if len(value) == 0 {
			value = limits.MaxTokens
		}
	case "max_tokens":
		value, remove = limits.MaxTokens, "max_completion_tokens"
		if len(value) == 0 {
			value = limits.MaxCompletionTokens
		}
	default:
		return nil, fmt.Errorf("unsupported Chat token limit target %q", targetField)
	}
	if len(value) == 0 {
		return body, nil
	}
	plan, err := planTopLevelJSONPatches(body, []topLevelJSONPatch{
		{name: targetField, value: append(json.RawMessage(nil), value...)},
		{name: remove},
	})
	if err != nil {
		return nil, err
	}
	return plan.apply(), nil
}

func splitLineEnding(line []byte) ([]byte, []byte) {
	if len(line) == 0 || line[len(line)-1] != '\n' {
		return line, nil
	}
	if len(line) >= 2 && line[len(line)-2] == '\r' {
		return line[:len(line)-2], []byte("\r\n")
	}
	return line[:len(line)-1], []byte("\n")
}

func rewriteProviderJSONModel(body []byte, model string) []byte {
	modelJSON, err := json.Marshal(model)
	if err != nil {
		return body
	}
	fields, _, err := scanTopLevelJSONObject(body)
	if err != nil {
		return body
	}
	for _, field := range fields {
		if field.name == "model" {
			plan, err := planTopLevelJSONPatches(body, []topLevelJSONPatch{{name: "model", value: modelJSON}})
			if err == nil {
				return plan.apply()
			}
			break
		}
	}
	return body
}

// rewriteChatCompletionModel replaces only the model field of the caller's
// original request JSON so the provider receives its served model name. The
// rest of the caller payload is forwarded without IOP rewriting.
func rewriteChatCompletionModel(rawBody []byte, target string, req chatCompletionRequest) ([]byte, error) {
	patches, err := chatCompletionProviderPatches(target, req)
	if err != nil {
		return nil, err
	}
	if len(patches) == 0 {
		return rawBody, nil
	}
	plan, err := planTopLevelJSONPatches(rawBody, patches)
	if err != nil {
		return nil, err
	}
	return plan.apply(), nil
}

func rewriteChatCompletionModelFromIngress(ingress *openAIIngressSnapshot, target string, req chatCompletionRequest) (*openAIRebuiltLease, error) {
	patches, err := chatCompletionProviderPatches(target, req)
	if err != nil {
		return nil, err
	}
	return rewriteIngressTopLevelJSON(ingress, patches)
}

func chatCompletionProviderPatches(target string, req chatCompletionRequest) ([]topLevelJSONPatch, error) {
	patches := make([]topLevelJSONPatch, 0, 5)
	if strings.TrimSpace(target) != "" {
		modelJSON, err := json.Marshal(target)
		if err != nil {
			return nil, err
		}
		patches = append(patches, topLevelJSONPatch{name: "model", value: modelJSON})
	}
	if req.MaxTokens != nil {
		maxTokensJSON, err := json.Marshal(*req.MaxTokens)
		if err != nil {
			return nil, err
		}
		patches = append(patches,
			topLevelJSONPatch{name: "max_tokens", value: maxTokensJSON},
			topLevelJSONPatch{name: "max_completion_tokens"},
		)
	}
	if req.ThinkingTokenBudget != nil {
		budgetJSON, err := json.Marshal(*req.ThinkingTokenBudget)
		if err != nil {
			return nil, err
		}
		patches = append(patches, topLevelJSONPatch{name: "thinking_token_budget", value: budgetJSON})
	}
	if req.Think != nil {
		thinkJSON, err := json.Marshal(*req.Think)
		if err != nil {
			return nil, err
		}
		patches = append(patches, topLevelJSONPatch{name: "think", value: thinkJSON})
	}
	return patches, nil
}

// rewriteResponsesModel replaces only the model field of the caller's original
// /v1/responses request JSON so the provider receives its served model name.
// Every other field (input, instructions, tools, max_output_tokens, and any
// Codex/Responses-specific field) is forwarded without IOP rewriting. An empty
// target leaves the body untouched; invalid JSON is rejected.
func rewriteResponsesModel(rawBody []byte, target string) ([]byte, error) {
	if strings.TrimSpace(target) == "" {
		return rawBody, nil
	}
	modelJSON, err := json.Marshal(target)
	if err != nil {
		return nil, err
	}
	plan, err := planTopLevelJSONPatches(rawBody, []topLevelJSONPatch{{name: "model", value: modelJSON}})
	if err != nil {
		return nil, err
	}
	return plan.apply(), nil
}

func rewriteResponsesModelFromIngress(ingress *openAIIngressSnapshot, target string) (*openAIRebuiltLease, error) {
	if strings.TrimSpace(target) == "" {
		return rewriteIngressTopLevelJSON(ingress, nil)
	}
	modelJSON, err := json.Marshal(target)
	if err != nil {
		return nil, err
	}
	return rewriteIngressTopLevelJSON(ingress, []topLevelJSONPatch{{name: "model", value: modelJSON}})
}

// rewriteIngressTopLevelJSON places both allocation checks around the single
// patched output and returns a lease that stays live through synchronous
// provider submission. The canonical ingress owner remains unchanged.
func rewriteIngressTopLevelJSON(ingress *openAIIngressSnapshot, patches []topLevelJSONPatch) (*openAIRebuiltLease, error) {
	body, err := ingress.canonicalBody()
	if err != nil {
		return nil, err
	}
	plan, err := planTopLevelJSONPatches(body, patches)
	if err != nil {
		return nil, err
	}
	if len(plan.edits) == 0 {
		return &openAIRebuiltLease{ingress: ingress, bodyAlias: body}, nil
	}
	guard, err := ingress.reserveRebuild(int64(plan.outputSize))
	if err != nil {
		return nil, err
	}
	output := plan.apply()
	rebuilt, err := guard.CommitOwnedTyped(openAIRebuiltBodyViewName, output)
	if err != nil {
		guard.Close()
		return nil, err
	}
	return &openAIRebuiltLease{
		ingress: ingress, guard: guard, rebuilt: rebuilt, bodyAlias: output,
	}, nil
}
