package openai

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

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

// providerRequestRequirements is the caller-neutral request shape used for
// provider operation selection. Agent or SDK identity is intentionally absent.
type providerRequestRequirements struct {
	Effort           string
	HasTools         bool
	HasTokenBudget   bool
	Stream           bool
	StructuredOutput bool
}

type providerOperationPlan struct {
	Operation  config.ProtocolOperation
	Effort     string
	EffortWire string
}

type openAIResponsesBridgeResponse struct {
	ID     string `json:"id"`
	Model  string `json:"model"`
	Status string `json:"status"`
	Output []struct {
		Type      string `json:"type"`
		ID        string `json:"id"`
		CallID    string `json:"call_id"`
		Name      string `json:"name"`
		Arguments string `json:"arguments"`
		Content   []struct {
			Type string `json:"type"`
			Text string `json:"text"`
		} `json:"content"`
		Summary []struct {
			Type string `json:"type"`
			Text string `json:"text"`
		} `json:"summary"`
	} `json:"output"`
	Usage struct {
		InputTokens  int `json:"input_tokens"`
		OutputTokens int `json:"output_tokens"`
		InputDetails struct {
			CachedTokens int `json:"cached_tokens"`
		} `json:"input_tokens_details"`
	} `json:"usage"`
	IncompleteDetails struct {
		Reason string `json:"reason"`
	} `json:"incomplete_details"`
}

// singleRequestChatBridgeResponse admits the portable Chat Completions response
// fields needed by private stages. Provider-selected bookkeeping such as
// service_tier, system_fingerprint, annotations, and logprobs is intentionally
// left outside the canonical stage envelope.
type singleRequestChatBridgeResponse struct {
	ID      string                          `json:"id"`
	Object  string                          `json:"object"`
	Created int64                           `json:"created"`
	Model   string                          `json:"model"`
	Choices []singleRequestChatBridgeChoice `json:"choices"`
	Usage   json.RawMessage                 `json:"usage"`
}

type singleRequestChatBridgeChoice struct {
	Index        int                            `json:"index"`
	FinishReason string                         `json:"finish_reason"`
	Message      singleRequestChatBridgeMessage `json:"message"`
}

type singleRequestChatBridgeMessage struct {
	Role             string          `json:"role"`
	Content          *string         `json:"content"`
	ToolCalls        json.RawMessage `json:"tool_calls"`
	ReasoningContent *string         `json:"reasoning_content"`
	ExtraContent     json.RawMessage `json:"extra_content"`
	Refusal          json.RawMessage `json:"refusal"`
}

func (v *singleRequestChatBridgeResponse) UnmarshalJSON(data []byte) error {
	if err := validateSingleRequestObjectFields(data, "id", "object", "created", "model", "choices", "usage", "service_tier", "system_fingerprint", "timings"); err != nil {
		return err
	}
	type alias singleRequestChatBridgeResponse
	var decoded alias
	if err := json.Unmarshal(data, &decoded); err != nil {
		return err
	}
	*v = singleRequestChatBridgeResponse(decoded)
	return nil
}

func (v *singleRequestChatBridgeChoice) UnmarshalJSON(data []byte) error {
	if err := validateSingleRequestObjectFields(data, "index", "finish_reason", "message", "logprobs"); err != nil {
		return err
	}
	type alias singleRequestChatBridgeChoice
	var decoded alias
	if err := json.Unmarshal(data, &decoded); err != nil {
		return err
	}
	*v = singleRequestChatBridgeChoice(decoded)
	return nil
}

func (v *singleRequestChatBridgeMessage) UnmarshalJSON(data []byte) error {
	if err := validateSingleRequestObjectFields(data, "role", "content", "tool_calls", "reasoning_content", "extra_content", "refusal", "annotations"); err != nil {
		return err
	}
	type alias singleRequestChatBridgeMessage
	var decoded alias
	if err := json.Unmarshal(data, &decoded); err != nil {
		return err
	}
	*v = singleRequestChatBridgeMessage(decoded)
	return nil
}

func singleRequestProviderRequirements(options map[string]any, tools []any, responseFormat *singleRequestProviderResponseFormat) providerRequestRequirements {
	requirements := providerRequestRequirements{HasTools: len(tools) > 0, StructuredOutput: responseFormat != nil}
	if effort, ok := options["reasoning_effort"].(string); ok {
		requirements.Effort = strings.TrimSpace(effort)
	}
	return requirements
}

func singleRequestProviderCandidatePredicate(frozen *edgeservice.SingleRequestStageDispatchBinding, requirements providerRequestRequirements) edgeservice.ProviderPoolCandidatePredicate {
	return func(candidate edgeservice.ProviderPoolCandidate) bool {
		if frozen == nil || candidate.ProtocolProfile == nil || candidate.ExecutionPath != string(edgeservice.ProviderPoolPathTunnel) {
			return false
		}
		if frozen.CandidatePredicate != nil && !frozen.CandidatePredicate(candidate) {
			return false
		}
		_, err := selectProviderOperation(*candidate.ProtocolProfile, config.OperationChatCompletions, requirements)
		return err == nil
	}
}

func singleRequestProviderTunnelPreparer(requirements providerRequestRequirements, buildChat singleRequestProviderBodyBuilder) func(edgeservice.SubmitProviderTunnelRequest, edgeservice.ProviderPoolCandidate) (edgeservice.SubmitProviderTunnelRequest, error) {
	return func(tunnel edgeservice.SubmitProviderTunnelRequest, candidate edgeservice.ProviderPoolCandidate) (edgeservice.SubmitProviderTunnelRequest, error) {
		if candidate.ProtocolProfile == nil || buildChat == nil {
			return tunnel, errProviderStageMissingBinding
		}
		plan, err := selectProviderOperation(*candidate.ProtocolProfile, config.OperationChatCompletions, requirements)
		if err != nil {
			return tunnel, err
		}
		tunnel.Operation = string(plan.Operation)
		switch plan.Operation {
		case config.OperationChatCompletions:
			tunnel.Path = "/v1/chat/completions"
		case config.OperationResponses:
			tunnel.Path = "/v1/responses"
			// The internal Chat-to-Responses bridge intentionally buffers one
			// canonical stage result. Keep transport metadata aligned with the
			// generated body's stream=false contract even when caller ingress is
			// streaming.
			tunnel.Stream = false
		default:
			return tunnel, errProviderStageMissingBinding
		}
		tunnel.BuildBody = func(target string) ([]byte, error) {
			body, err := buildChat(target)
			if err != nil {
				return nil, err
			}
			return normalizeSingleRequestProviderRequest(body, plan)
		}
		return tunnel, nil
	}
}

func normalizeSingleRequestProviderRequest(chatBody []byte, plan providerOperationPlan) ([]byte, error) {
	var body map[string]any
	if err := json.Unmarshal(chatBody, &body); err != nil {
		return nil, errProviderStageMalformed
	}
	if plan.Operation == config.OperationChatCompletions {
		if plan.Effort != "" {
			body["reasoning_effort"] = plan.Effort
		}
		return json.Marshal(body)
	}
	if plan.Operation != config.OperationResponses {
		return nil, errProviderStageMalformed
	}
	return singleRequestChatToResponses(body, plan)
}

func singleRequestChatToResponses(chat map[string]any, plan providerOperationPlan) ([]byte, error) {
	responses := map[string]any{"model": chat["model"], "stream": false}
	for _, key := range []string{"service_tier"} {
		if value, exists := chat[key]; exists {
			responses[key] = value
		}
	}
	// OpenAI reasoning-capable Responses models reject Chat sampling controls.
	// When the profile selected an explicit reasoning effort, the effort is the
	// supported generation control and generic SDK temperature/top_p defaults
	// must not leak onto the provider wire.
	if plan.Effort == "" {
		for _, key := range []string{"temperature", "top_p"} {
			if value, exists := chat[key]; exists {
				responses[key] = value
			}
		}
	}
	for _, key := range []string{"max_completion_tokens", "max_tokens"} {
		if value, exists := chat[key]; exists {
			responses["max_output_tokens"] = value
			break
		}
	}
	messages, ok := chat["messages"].([]any)
	if !ok {
		return nil, errProviderStageMalformed
	}
	input := make([]any, 0, len(messages))
	instructions := make([]string, 0, 1)
	for _, raw := range messages {
		message, ok := raw.(map[string]any)
		if !ok {
			return nil, errProviderStageMalformed
		}
		role, _ := message["role"].(string)
		content, _ := message["content"].(string)
		switch role {
		case "system":
			if content != "" {
				instructions = append(instructions, content)
			}
		case "user":
			input = append(input, map[string]any{"type": "message", "role": "user", "content": []any{map[string]any{"type": "input_text", "text": content}}})
		case "assistant":
			if content != "" {
				input = append(input, map[string]any{"type": "message", "role": "assistant", "content": []any{map[string]any{"type": "output_text", "text": content}}})
			}
			for _, rawCall := range anySlice(message["tool_calls"]) {
				call, ok := rawCall.(map[string]any)
				function, functionOK := call["function"].(map[string]any)
				if !ok || !functionOK {
					return nil, errProviderStageMalformed
				}
				input = append(input, map[string]any{"type": "function_call", "call_id": call["id"], "name": function["name"], "arguments": function["arguments"]})
			}
		case "tool":
			input = append(input, map[string]any{"type": "function_call_output", "call_id": message["tool_call_id"], "output": content})
		default:
			return nil, errProviderStageMalformed
		}
	}
	if len(instructions) > 0 {
		responses["instructions"] = strings.Join(instructions, "\n\n")
	}
	responses["input"] = input
	if plan.Effort != "" {
		responses["reasoning"] = map[string]any{"effort": plan.Effort}
	}
	if rawTools := anySlice(chat["tools"]); len(rawTools) > 0 {
		tools := make([]any, 0, len(rawTools))
		for _, rawTool := range rawTools {
			tool, ok := rawTool.(map[string]any)
			function, functionOK := tool["function"].(map[string]any)
			if !ok || !functionOK {
				return nil, errProviderStageMalformed
			}
			converted := map[string]any{"type": "function", "name": function["name"], "parameters": function["parameters"]}
			for _, key := range []string{"description", "strict"} {
				if value, exists := function[key]; exists {
					converted[key] = value
				}
			}
			tools = append(tools, converted)
		}
		responses["tools"] = tools
	}
	if choice, ok := chat["tool_choice"]; ok {
		responses["tool_choice"] = singleRequestResponsesToolChoice(choice)
	}
	if parallel, ok := chat["parallel_tool_calls"]; ok {
		responses["parallel_tool_calls"] = parallel
	}
	if format, ok := chat["response_format"].(map[string]any); ok {
		if schema, schemaOK := format["json_schema"].(map[string]any); schemaOK {
			converted := map[string]any{"type": "json_schema"}
			for _, key := range []string{"name", "strict", "schema"} {
				converted[key] = schema[key]
			}
			responses["text"] = map[string]any{"format": converted}
		}
	}
	return json.Marshal(responses)
}

func anySlice(value any) []any {
	if value == nil {
		return nil
	}
	items, _ := value.([]any)
	return items
}

func singleRequestResponsesToolChoice(choice any) any {
	if object, ok := choice.(map[string]any); ok {
		if function, functionOK := object["function"].(map[string]any); functionOK {
			return map[string]any{"type": "function", "name": function["name"]}
		}
	}
	return choice
}

func normalizeSingleRequestProviderResponse(body []byte, dispatch edgeservice.RunDispatch) ([]byte, error) {
	switch dispatch.ProfileOperation {
	case string(config.OperationResponses):
		return normalizeSingleRequestResponsesResponse(body)
	case string(config.OperationChatCompletions), "":
		return normalizeSingleRequestChatResponse(body)
	default:
		return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
	}
}

func normalizeSingleRequestChatResponse(body []byte) ([]byte, error) {
	if err := validateSingleRequestJSON(body); err != nil {
		return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
	}
	var response singleRequestChatBridgeResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
	}
	choices := make([]any, 0, len(response.Choices))
	for _, choice := range response.Choices {
		if refusal := bytes.TrimSpace(choice.Message.Refusal); len(refusal) > 0 && !bytes.Equal(refusal, []byte("null")) {
			return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
		}
		message := map[string]any{
			"role":    choice.Message.Role,
			"content": choice.Message.Content,
		}
		if len(choice.Message.ToolCalls) > 0 {
			message["tool_calls"] = choice.Message.ToolCalls
		}
		if choice.Message.ReasoningContent != nil {
			message["reasoning_content"] = choice.Message.ReasoningContent
		}
		if len(choice.Message.ExtraContent) > 0 {
			message["extra_content"] = choice.Message.ExtraContent
		}
		choices = append(choices, map[string]any{
			"index": choice.Index, "finish_reason": choice.FinishReason, "message": message,
		})
	}
	canonical := map[string]any{
		"id": response.ID, "object": response.Object, "created": response.Created,
		"model": response.Model, "choices": choices,
	}
	if len(response.Usage) > 0 && !bytes.Equal(bytes.TrimSpace(response.Usage), []byte("null")) {
		canonical["usage"] = response.Usage
	}
	encoded, err := json.Marshal(canonical)
	if err != nil {
		return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
	}
	return encoded, nil
}

func normalizeSingleRequestResponsesResponse(body []byte) ([]byte, error) {
	var response openAIResponsesBridgeResponse
	if err := json.Unmarshal(body, &response); err != nil || strings.TrimSpace(response.ID) == "" {
		return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
	}
	content := ""
	toolCalls := make([]any, 0)
	for _, item := range response.Output {
		switch item.Type {
		case "message":
			for _, part := range item.Content {
				if part.Type == "output_text" {
					content += part.Text
				}
			}
		case "function_call":
			if item.CallID == "" || item.Name == "" || !json.Valid([]byte(item.Arguments)) {
				return nil, errors.Join(errProviderStageGeneric, errProviderStageMalformed)
			}
			toolCalls = append(toolCalls, map[string]any{"id": item.CallID, "type": "function", "function": map[string]any{"name": item.Name, "arguments": item.Arguments}})
		}
	}
	finishReason := "stop"
	if len(toolCalls) > 0 {
		finishReason = "tool_calls"
	} else if response.Status == "incomplete" && response.IncompleteDetails.Reason == "max_output_tokens" {
		finishReason = "length"
	}
	var contentValue any = content
	if len(toolCalls) > 0 && content == "" {
		contentValue = nil
	}
	chat := map[string]any{
		"id": response.ID, "object": "chat.completion", "created": int64(0), "model": response.Model,
		"choices": []any{map[string]any{"index": 0, "finish_reason": finishReason, "message": map[string]any{"role": "assistant", "content": contentValue, "tool_calls": toolCalls}}},
	}
	return json.Marshal(chat)
}

func anthropicProviderRequirements(req anthropicMessageRequest) providerRequestRequirements {
	requirements := providerRequestRequirements{
		HasTools:         len(req.Tools) > 0,
		Stream:           req.Stream,
		HasTokenBudget:   req.Thinking != nil && req.Thinking.Type == "enabled",
		StructuredOutput: req.OutputConfig != nil && req.OutputConfig.Format != nil,
	}
	if req.OutputConfig != nil {
		requirements.Effort = strings.TrimSpace(req.OutputConfig.Effort)
	}
	return requirements
}

func decodeAnthropicProviderRequirements(body []byte) (providerRequestRequirements, error) {
	var request struct {
		Stream       bool                     `json:"stream"`
		Tools        []json.RawMessage        `json:"tools"`
		Thinking     *anthropicThinkingConfig `json:"thinking"`
		OutputConfig *anthropicOutputConfig   `json:"output_config"`
	}
	if err := json.Unmarshal(body, &request); err != nil {
		return providerRequestRequirements{}, fmt.Errorf("decode Messages request")
	}
	return anthropicProviderRequirements(anthropicMessageRequest{
		Stream: request.Stream, Tools: make([]anthropicTool, len(request.Tools)),
		Thinking: request.Thinking, OutputConfig: request.OutputConfig,
	}), nil
}

func decodeResponsesProviderRequirements(body []byte) (providerRequestRequirements, error) {
	var request struct {
		Stream    bool              `json:"stream"`
		Tools     []json.RawMessage `json:"tools"`
		Reasoning *struct {
			Effort string `json:"effort"`
		} `json:"reasoning"`
		MaxOutputTokens *int                 `json:"max_output_tokens"`
		Text            *responsesTextFormat `json:"text"`
	}
	if err := json.Unmarshal(body, &request); err != nil {
		return providerRequestRequirements{}, fmt.Errorf("decode Responses request")
	}
	requirements := providerRequestRequirements{HasTools: len(request.Tools) > 0, Stream: request.Stream}
	if request.MaxOutputTokens != nil {
		requirements.HasTokenBudget = true
	}
	if request.Text != nil && request.Text.Format != nil {
		requirements.StructuredOutput = true
	}
	if request.Reasoning != nil {
		requirements.Effort = strings.TrimSpace(request.Reasoning.Effort)
	}
	return requirements, nil
}

// responsesTextFormat mirrors the Responses request text.format selector that
// carries structured-output constraints. It is intentionally minimal: only the
// presence of a format block is needed for admission decisions.
type responsesTextFormat struct {
	Format json.RawMessage `json:"format,omitempty"`
}

func responsesCandidatePredicate(requirements providerRequestRequirements) edgeservice.ProviderPoolCandidatePredicate {
	return func(candidate edgeservice.ProviderPoolCandidate) bool {
		// A nil profile is the legacy tunnel contract: operation resolution is
		// deferred to the existing dispatch path, which must remain compatible.
		if candidate.ProtocolProfile == nil {
			return true
		}
		if candidate.ExecutionPath != "" && candidate.ExecutionPath != string(edgeservice.ProviderPoolPathTunnel) {
			return requirements.Effort == "" && !requirements.HasTools && !requirements.HasTokenBudget
		}
		_, err := selectProviderOperation(*candidate.ProtocolProfile, config.OperationResponses, requirements)
		return err == nil
	}
}

func rewriteResponsesProviderControls(body []byte, target string, plan providerOperationPlan) ([]byte, error) {
	patches := make([]topLevelJSONPatch, 0, 2)
	if strings.TrimSpace(target) != "" {
		modelJSON, err := json.Marshal(target)
		if err != nil {
			return nil, err
		}
		patches = append(patches, topLevelJSONPatch{name: "model", value: modelJSON})
	}
	if plan.Effort != "" {
		var root map[string]json.RawMessage
		if err := json.Unmarshal(body, &root); err != nil {
			return nil, fmt.Errorf("decode Responses request")
		}
		var reasoning map[string]any
		if raw := root["reasoning"]; len(raw) > 0 && string(raw) != "null" {
			if err := json.Unmarshal(raw, &reasoning); err != nil {
				return nil, fmt.Errorf("reasoning must be an object")
			}
		}
		if reasoning == nil {
			reasoning = make(map[string]any)
		}
		reasoning["effort"] = plan.Effort
		reasoningJSON, err := json.Marshal(reasoning)
		if err != nil {
			return nil, err
		}
		patches = append(patches, topLevelJSONPatch{name: "reasoning", value: reasoningJSON})
	}
	if len(patches) == 0 {
		return body, nil
	}
	patchPlan, err := planTopLevelJSONPatches(body, patches)
	if err != nil {
		return nil, err
	}
	return patchPlan.apply(), nil
}

// prepareResponsesMessagesBridge and prepareResponsesChatBridge deliberately
// accept only the portable subset below. A Responses tunnel is otherwise
// lossless, but a bridge must never silently discard a caller control.
func prepareResponsesMessagesBridge(body []byte, target string, profile config.ConcreteProtocolProfile, plan providerOperationPlan) ([]byte, error) {
	if plan.Operation != config.OperationMessages || (plan.Effort != "" && plan.EffortWire != config.ProtocolEffortWireAnthropicMessage) {
		return nil, fmt.Errorf("selected Messages profile cannot preserve the requested Responses controls")
	}
	root, err := decodeResponsesBridgeRoot(body)
	if err != nil {
		return nil, err
	}
	messages, system, err := responsesInputToBridgeMessages(root["input"], true)
	if err != nil {
		return nil, err
	}
	if instructions, _ := root["instructions"].(string); strings.TrimSpace(instructions) != "" {
		system = append([]string{instructions}, system...)
	}
	maxTokens, ok := root["max_output_tokens"]
	if !ok {
		return nil, fmt.Errorf("max_output_tokens is required for the Messages bridge")
	}
	maxTokensNumber, ok := maxTokens.(float64)
	if !ok || maxTokensNumber <= 0 || maxTokensNumber != float64(int(maxTokensNumber)) {
		return nil, fmt.Errorf("max_output_tokens must be a positive integer for the Messages bridge")
	}
	request := map[string]any{"model": target, "messages": messages, "max_tokens": maxTokens}
	if len(system) > 0 {
		request["system"] = strings.Join(system, "\n\n")
	}
	copyResponsesBridgeOption(root, request, "stream", "temperature", "top_p")
	if plan.Effort != "" {
		request["output_config"] = map[string]any{"effort": plan.Effort}
	}
	if err := copyResponsesToolsToMessages(root, request); err != nil {
		return nil, err
	}
	if format, ok := responsesStructuredFormat(root); ok {
		output, _ := request["output_config"].(map[string]any)
		if output == nil {
			output = map[string]any{}
		}
		output["format"] = format
		request["output_config"] = output
	}
	return json.Marshal(request)
}

func prepareResponsesChatBridge(body []byte, target string, profile config.ConcreteProtocolProfile, plan providerOperationPlan) ([]byte, error) {
	if plan.Operation != config.OperationChatCompletions || (plan.Effort != "" && plan.EffortWire != config.ProtocolEffortWireOpenAIChat && plan.EffortWire != config.ProtocolEffortWireGeminiChat) {
		return nil, fmt.Errorf("selected Chat profile cannot preserve the requested Responses controls")
	}
	root, err := decodeResponsesBridgeRoot(body)
	if err != nil {
		return nil, err
	}
	messages, system, err := responsesInputToBridgeMessages(root["input"], false)
	if err != nil {
		return nil, err
	}
	if instructions, _ := root["instructions"].(string); strings.TrimSpace(instructions) != "" {
		system = append([]string{instructions}, system...)
	}
	if len(system) > 0 {
		messages = append([]any{map[string]any{"role": "system", "content": strings.Join(system, "\n\n")}}, messages...)
	}
	request := map[string]any{"model": target, "messages": messages}
	copyResponsesBridgeOption(root, request, "stream", "temperature", "top_p", "parallel_tool_calls")
	if max, ok := root["max_output_tokens"]; ok {
		field := "max_tokens"
		if mapping, ok := profile.EffortMapping(config.OperationChatCompletions); ok && mapping.Wire == config.ProtocolEffortWireOpenAIChat {
			field = "max_completion_tokens"
		}
		request[field] = max
	}
	if plan.Effort != "" {
		request["reasoning_effort"] = plan.Effort
	}
	if err := copyResponsesToolsToChat(root, request); err != nil {
		return nil, err
	}
	if format, ok := responsesStructuredFormat(root); ok {
		request["response_format"] = map[string]any{"type": "json_schema", "json_schema": format}
	}
	return json.Marshal(request)
}

func decodeResponsesBridgeRoot(body []byte) (map[string]any, error) {
	var root map[string]any
	if err := json.Unmarshal(body, &root); err != nil {
		return nil, fmt.Errorf("decode Responses bridge request")
	}
	allowed := map[string]bool{"model": true, "input": true, "instructions": true, "stream": true, "max_output_tokens": true, "temperature": true, "top_p": true, "tools": true, "tool_choice": true, "parallel_tool_calls": true, "reasoning": true, "text": true}
	for key := range root {
		if !allowed[key] {
			return nil, fmt.Errorf("Responses field %q is not representable by the selected provider wire", key)
		}
	}
	if _, ok := root["input"]; !ok {
		return nil, fmt.Errorf("input is required")
	}
	return root, nil
}

func validateResponsesBridgeControls(body []byte) error {
	_, err := decodeResponsesBridgeRoot(body)
	return err
}

func copyResponsesBridgeOption(source, target map[string]any, keys ...string) {
	for _, key := range keys {
		if value, ok := source[key]; ok {
			target[key] = value
		}
	}
}

func responsesInputToBridgeMessages(input any, messagesWire bool) ([]any, []string, error) {
	if text, ok := input.(string); ok {
		return []any{map[string]any{"role": "user", "content": text}}, nil, nil
	}
	items, ok := input.([]any)
	if !ok {
		return nil, nil, fmt.Errorf("input must be a string or an item array")
	}
	var messages []any
	var system []string
	for _, raw := range items {
		item, ok := raw.(map[string]any)
		if !ok {
			return nil, nil, fmt.Errorf("input item is invalid")
		}
		typ, _ := item["type"].(string)
		switch typ {
		case "message":
			role, _ := item["role"].(string)
			text, err := responsesMessageText(item["content"])
			if err != nil {
				return nil, nil, err
			}
			if role == "system" {
				system = append(system, text)
				continue
			}
			if role != "user" && role != "assistant" {
				return nil, nil, fmt.Errorf("message role %q is not representable", role)
			}
			messages = append(messages, map[string]any{"role": role, "content": text})
		case "function_call":
			id, _ := item["call_id"].(string)
			name, _ := item["name"].(string)
			args, _ := item["arguments"].(string)
			if id == "" || name == "" || !json.Valid([]byte(args)) {
				return nil, nil, fmt.Errorf("function_call is invalid")
			}
			if messagesWire {
				messages = append(messages, map[string]any{"role": "assistant", "content": []any{map[string]any{"type": "tool_use", "id": id, "name": name, "input": json.RawMessage(args)}}})
			} else {
				messages = append(messages, map[string]any{"role": "assistant", "content": nil, "tool_calls": []any{map[string]any{"id": id, "type": "function", "function": map[string]any{"name": name, "arguments": args}}}})
			}
		case "function_call_output":
			id, _ := item["call_id"].(string)
			output, ok := item["output"].(string)
			if id == "" || !ok {
				return nil, nil, fmt.Errorf("function_call_output is invalid")
			}
			if messagesWire {
				messages = append(messages, map[string]any{"role": "user", "content": []any{map[string]any{"type": "tool_result", "tool_use_id": id, "content": output}}})
			} else {
				original, signature, encoded, err := decodeGeminiThoughtSignatureToolID(id)
				if err != nil {
					return nil, nil, err
				}
				tool := map[string]any{"role": "tool", "tool_call_id": original, "content": output}
				if encoded {
					tool["extra_content"] = openAIChatThoughtSignature(signature)
				}
				messages = append(messages, tool)
			}
		default:
			return nil, nil, fmt.Errorf("input item type %q is not representable", typ)
		}
	}
	return messages, system, nil
}

func responsesMessageText(raw any) (string, error) {
	if text, ok := raw.(string); ok {
		return text, nil
	}
	parts, ok := raw.([]any)
	if !ok {
		return "", fmt.Errorf("message content is invalid")
	}
	var out []string
	for _, rawPart := range parts {
		part, ok := rawPart.(map[string]any)
		if !ok {
			return "", fmt.Errorf("message content part is invalid")
		}
		typ, _ := part["type"].(string)
		text, _ := part["text"].(string)
		if (typ != "input_text" && typ != "output_text" && typ != "text") || text == "" {
			return "", fmt.Errorf("message content part %q is not representable", typ)
		}
		out = append(out, text)
	}
	return strings.Join(out, "\n"), nil
}

func copyResponsesToolsToChat(root, request map[string]any) error {
	tools, exists := root["tools"]
	if !exists {
		return copyResponsesToolChoice(root, request, false)
	}
	items, ok := tools.([]any)
	if !ok {
		return fmt.Errorf("tools must be an array")
	}
	out := make([]any, 0, len(items))
	for _, raw := range items {
		tool, ok := raw.(map[string]any)
		if !ok || tool["type"] != "function" {
			return fmt.Errorf("tool is not representable by the Chat bridge")
		}
		name, _ := tool["name"].(string)
		params, ok := tool["parameters"]
		if name == "" || !ok {
			return fmt.Errorf("tool is invalid")
		}
		fn := map[string]any{"name": name, "parameters": params}
		if description, ok := tool["description"]; ok {
			fn["description"] = description
		}
		out = append(out, map[string]any{"type": "function", "function": fn})
	}
	request["tools"] = out
	return copyResponsesToolChoice(root, request, false)
}

func copyResponsesToolsToMessages(root, request map[string]any) error {
	tools, exists := root["tools"]
	if !exists {
		return copyResponsesToolChoice(root, request, true)
	}
	items, ok := tools.([]any)
	if !ok {
		return fmt.Errorf("tools must be an array")
	}
	out := make([]any, 0, len(items))
	for _, raw := range items {
		tool, ok := raw.(map[string]any)
		if !ok || tool["type"] != "function" {
			return fmt.Errorf("tool is not representable by the Messages bridge")
		}
		name, _ := tool["name"].(string)
		schema, ok := tool["parameters"]
		if name == "" || !ok {
			return fmt.Errorf("tool is invalid")
		}
		converted := map[string]any{"name": name, "input_schema": schema}
		if description, ok := tool["description"]; ok {
			converted["description"] = description
		}
		out = append(out, converted)
	}
	request["tools"] = out
	return copyResponsesToolChoice(root, request, true)
}

func copyResponsesToolChoice(root, request map[string]any, messagesWire bool) error {
	choice, exists := root["tool_choice"]
	if !exists {
		return nil
	}
	if text, ok := choice.(string); ok {
		if text != "auto" && text != "required" && text != "none" {
			return fmt.Errorf("tool_choice %q is not representable", text)
		}
		if messagesWire {
			if text == "none" {
				return fmt.Errorf("tool_choice none is not representable by Messages")
			}
			request["tool_choice"] = map[string]any{"type": text}
		} else {
			request["tool_choice"] = text
		}
		return nil
	}
	selected, ok := choice.(map[string]any)
	if !ok || selected["type"] != "function" {
		return fmt.Errorf("tool_choice is invalid")
	}
	name, _ := selected["name"].(string)
	if name == "" {
		return fmt.Errorf("tool_choice function name is required")
	}
	if messagesWire {
		request["tool_choice"] = map[string]any{"type": "tool", "name": name}
	} else {
		request["tool_choice"] = map[string]any{"type": "function", "function": map[string]any{"name": name}}
	}
	return nil
}

func responsesStructuredFormat(root map[string]any) (map[string]any, bool) {
	text, ok := root["text"].(map[string]any)
	if !ok {
		return nil, false
	}
	format, ok := text["format"].(map[string]any)
	return format, ok
}

// selectProviderOperation chooses an operation solely from normalized request
// requirements and the selected provider profile. The order prefers the
// closest wire surface, but only an operation that preserves every declared
// requirement is eligible.
func selectProviderOperation(profile config.ConcreteProtocolProfile, ingress config.ProtocolOperation, requirements providerRequestRequirements) (providerOperationPlan, error) {
	operations := []config.ProtocolOperation{ingress}
	switch ingress {
	case config.OperationResponses:
		switch profile.Driver {
		case config.ProtocolDriverOpenAIResponses:
			operations = []config.ProtocolOperation{config.OperationResponses}
		case config.ProtocolDriverAnthropicMessages:
			operations = []config.ProtocolOperation{config.OperationMessages}
		case config.ProtocolDriverOpenAIChat:
			// A Chat driver may still declare its native Responses operation
			// (the OpenAI profile does). Prefer that lossless wire; profiles such
			// as Gemini that do not declare it use the Chat bridge.
			if _, ok := profile.Operations[string(config.OperationResponses)]; ok {
				operations = []config.ProtocolOperation{config.OperationResponses}
			} else {
				operations = []config.ProtocolOperation{config.OperationChatCompletions}
			}
		default:
			operations = nil
		}
	case config.OperationMessages:
		switch profile.Driver {
		case config.ProtocolDriverAnthropicMessages:
			operations = []config.ProtocolOperation{config.OperationMessages}
		case config.ProtocolDriverOpenAIChat:
			operations = []config.ProtocolOperation{config.OperationChatCompletions, config.OperationResponses}
		case config.ProtocolDriverOpenAIResponses:
			operations = []config.ProtocolOperation{config.OperationResponses}
		default:
			operations = nil
		}
	case config.OperationChatCompletions:
		switch profile.Driver {
		case config.ProtocolDriverOpenAIChat:
			operations = []config.ProtocolOperation{config.OperationChatCompletions, config.OperationResponses}
		case config.ProtocolDriverOpenAIResponses:
			operations = []config.ProtocolOperation{config.OperationResponses}
		default:
			operations = nil
		}
	}

	for _, operation := range operations {
		if _, ok := profile.Operations[string(operation)]; !ok {
			continue
		}
		if required := operationRequiredCapability(operation); required != "" && !profile.HasCapability(required) {
			continue
		}
		if requirements.Stream && !profile.HasCapability("streaming") {
			continue
		}
		if requirements.HasTools && !profile.HasCapability("tool_calling") {
			continue
		}

		plan := providerOperationPlan{Operation: operation}
		mapping, hasMapping := profile.EffortMapping(operation)
		supportsTokenBudget := hasMapping && mapping.TokenBudget
		if operation == config.OperationChatCompletions && profileSupportsAnthropicThinking(profile) {
			supportsTokenBudget = true
		}
		if requirements.HasTokenBudget && operation != ingress && !supportsTokenBudget {
			continue
		}
		// Native ingress wire already carries this control verbatim. Capability
		// mapping is required only when a bridge must translate it.
		// Token output limits have direct fields on bridge wires; only an
		// explicit reasoning effort needs the profile's token-budget mapping.
		if requirements.Effort != "" {
			mapped, ok := profile.MapReasoningEffort(operation, requirements.Effort, requirements.HasTools)
			if !ok {
				continue
			}
			plan.Effort = mapped
			plan.EffortWire = mapping.Wire
		}
		return plan, nil
	}

	return providerOperationPlan{}, fmt.Errorf("protocol profile %q cannot preserve the requested operation, tools, and reasoning controls", profile.ID)
}

func operationRequiredCapability(operation config.ProtocolOperation) string {
	switch operation {
	case config.OperationMessages:
		return "messages"
	case config.OperationChatCompletions:
		return "chat"
	case config.OperationResponses:
		return "responses"
	case config.OperationCountTokens:
		return "count_tokens"
	default:
		return ""
	}
}

func prepareAnthropicResponsesBridge(body []byte, target string, profile config.ConcreteProtocolProfile, plan providerOperationPlan) ([]byte, anthropicMessageRequest, error) {
	req, err := decodeAnthropicMessageRequest(body, true)
	if err != nil {
		return nil, req, err
	}
	if plan.Operation != config.OperationResponses || (plan.Effort != "" && plan.EffortWire != config.ProtocolEffortWireOpenAIResponses) {
		return nil, req, fmt.Errorf("selected operation has incompatible Responses effort normalization")
	}
	if req.TopK != nil {
		return nil, req, fmt.Errorf("top_k is not supported by the Responses bridge")
	}
	if req.Thinking != nil && req.Thinking.Type == "enabled" {
		return nil, req, fmt.Errorf("selected Responses profile does not support an explicit thinking token budget")
	}

	input := make([]map[string]any, 0, len(req.Messages))
	for index, message := range req.Messages {
		blocks, err := decodeAnthropicContent(message.Content)
		if err != nil {
			return nil, req, fmt.Errorf("messages[%d].content: %w", index, err)
		}
		converted, err := anthropicMessageToResponses(message.Role, blocks)
		if err != nil {
			return nil, req, fmt.Errorf("messages[%d]: %w", index, err)
		}
		input = append(input, converted...)
	}

	responses := map[string]any{
		"model":             target,
		"input":             input,
		"max_output_tokens": *req.MaxTokens,
		"stream":            req.Stream,
	}
	if system, err := decodeAnthropicSystem(req.System); err != nil {
		return nil, req, err
	} else if len(system) > 0 {
		parts := make([]string, 0, len(system))
		for _, block := range system {
			parts = append(parts, block.Text)
		}
		responses["instructions"] = strings.Join(parts, "\n")
	}
	if plan.Effort != "" {
		responses["reasoning"] = map[string]any{"effort": plan.Effort}
	}
	if req.Temperature != nil {
		responses["temperature"] = *req.Temperature
	}
	if req.TopP != nil {
		responses["top_p"] = *req.TopP
	}
	if len(req.StopSequences) > 0 {
		return nil, req, fmt.Errorf("stop_sequences is not supported by the Responses bridge")
	}
	if len(req.Tools) > 0 {
		tools := make([]map[string]any, 0, len(req.Tools))
		for _, tool := range req.Tools {
			var schema map[string]any
			if err := json.Unmarshal(tool.InputSchema, &schema); err != nil {
				return nil, req, fmt.Errorf("tool %q input_schema is invalid", tool.Name)
			}
			converted := map[string]any{"type": "function", "name": tool.Name, "parameters": schema}
			if tool.Description != "" {
				converted["description"] = tool.Description
			}
			tools = append(tools, converted)
		}
		responses["tools"] = tools
	}
	if req.ToolChoice != nil {
		choice, parallel := anthropicToolChoiceToResponses(*req.ToolChoice)
		responses["tool_choice"] = choice
		if parallel != nil {
			responses["parallel_tool_calls"] = *parallel
		}
	}
	if req.OutputConfig != nil && req.OutputConfig.Format != nil {
		var schema map[string]any
		if err := json.Unmarshal(req.OutputConfig.Format.Schema, &schema); err != nil {
			return nil, req, fmt.Errorf("decode output_config.format.schema: %w", err)
		}
		responses["text"] = map[string]any{"format": map[string]any{
			"type": "json_schema", "name": "response", "strict": true, "schema": schema,
		}}
	}

	encoded, err := json.Marshal(responses)
	if err != nil {
		return nil, req, fmt.Errorf("encode Responses bridge request: %w", err)
	}
	return encoded, req, nil
}

func anthropicMessageToResponses(role string, blocks []anthropicContentBlock) ([]map[string]any, error) {
	if role == "assistant" {
		out := make([]map[string]any, 0, len(blocks))
		for _, block := range blocks {
			switch block.Type {
			case "text":
				out = append(out, map[string]any{"type": "message", "role": "assistant", "content": []map[string]any{{"type": "output_text", "text": block.Text}}})
			case "thinking":
				if block.Signature != "" {
					return nil, fmt.Errorf("signed thinking blocks cannot be represented by the Responses bridge")
				}
			case "tool_use":
				callID, _, _ := decodeAnthropicBridgeToolID(block.ID)
				out = append(out, map[string]any{"type": "function_call", "call_id": callID, "name": block.Name, "arguments": string(block.Input)})
			default:
				return nil, fmt.Errorf("content block %q is invalid for an assistant message", block.Type)
			}
		}
		return out, nil
	}
	if role == "system" {
		content := make([]map[string]any, 0, len(blocks))
		for _, block := range blocks {
			if block.Type != "text" {
				return nil, fmt.Errorf("content block %q is invalid for a system message", block.Type)
			}
			content = append(content, map[string]any{"type": "input_text", "text": block.Text})
		}
		if len(content) == 0 {
			return nil, fmt.Errorf("system message content is empty")
		}
		return []map[string]any{{"type": "message", "role": "system", "content": content}}, nil
	}

	out := make([]map[string]any, 0, len(blocks))
	content := make([]map[string]any, 0, len(blocks))
	flushContent := func() {
		if len(content) > 0 {
			out = append(out, map[string]any{"type": "message", "role": "user", "content": content})
			content = nil
		}
	}
	for _, block := range blocks {
		switch block.Type {
		case "text":
			content = append(content, map[string]any{"type": "input_text", "text": block.Text})
		case "image":
			imageURL := block.Source.URL
			if block.Source.Type == "base64" {
				imageURL = "data:" + block.Source.MediaType + ";base64," + block.Source.Data
			}
			content = append(content, map[string]any{"type": "input_image", "image_url": imageURL})
		case "tool_result":
			flushContent()
			result, err := anthropicToolResultText(block.Content)
			if err != nil {
				return nil, err
			}
			if block.IsError {
				result = "Error: " + result
			}
			callID, _, _ := decodeAnthropicBridgeToolID(block.ToolUseID)
			out = append(out, map[string]any{"type": "function_call_output", "call_id": callID, "output": result})
		default:
			return nil, fmt.Errorf("content block %q is invalid for a user message", block.Type)
		}
	}
	flushContent()
	if len(out) == 0 {
		return nil, fmt.Errorf("user message content is empty")
	}
	return out, nil
}

func anthropicToolChoiceToResponses(choice anthropicToolChoice) (any, *bool) {
	parallel := !choice.DisableParallelToolUse
	switch choice.Type {
	case "auto":
		return "auto", &parallel
	case "any":
		return "required", &parallel
	case "none":
		return "none", &parallel
	default:
		return map[string]any{"type": "function", "name": choice.Name}, &parallel
	}
}

func convertResponsesResponseToAnthropic(body []byte, requestModel string) (anthropicMessageResponse, error) {
	var response openAIResponsesBridgeResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return anthropicMessageResponse{}, fmt.Errorf("decode Responses response: %w", err)
	}
	if strings.TrimSpace(response.ID) == "" {
		return anthropicMessageResponse{}, fmt.Errorf("Responses response has no id")
	}
	content := make([]map[string]any, 0, len(response.Output))
	hasTools := false
	for _, item := range response.Output {
		switch item.Type {
		case "message":
			for _, part := range item.Content {
				if part.Type == "output_text" && part.Text != "" {
					content = append(content, map[string]any{"type": "text", "text": part.Text})
				}
			}
		case "reasoning":
			for _, part := range item.Summary {
				if part.Text != "" {
					content = append(content, map[string]any{"type": "thinking", "thinking": part.Text, "signature": ""})
				}
			}
		case "function_call":
			if item.CallID == "" || item.Name == "" || !json.Valid([]byte(item.Arguments)) {
				return anthropicMessageResponse{}, fmt.Errorf("Responses function call has invalid call_id, name, or arguments")
			}
			var input any
			if err := json.Unmarshal([]byte(item.Arguments), &input); err != nil {
				return anthropicMessageResponse{}, fmt.Errorf("decode Responses function arguments: %w", err)
			}
			content = append(content, map[string]any{
				"type": "tool_use", "id": encodeAnthropicBridgeToolID(item.CallID, openAIChatToolExtraContent{}),
				"name": item.Name, "input": input,
			})
			hasTools = true
		}
	}
	stopReason := "end_turn"
	if hasTools {
		stopReason = "tool_use"
	} else if response.Status == "incomplete" && response.IncompleteDetails.Reason == "max_output_tokens" {
		stopReason = "max_tokens"
	}
	model := requestModel
	if model == "" {
		model = response.Model
	}
	return anthropicMessageResponse{
		ID: response.ID, Type: "message", Role: "assistant", Model: model, Content: content,
		StopReason: &stopReason,
		Usage: anthropicUsage{
			InputTokens: response.Usage.InputTokens, OutputTokens: response.Usage.OutputTokens,
			CacheReadInputTokens: response.Usage.InputDetails.CachedTokens,
		},
	}, nil
}
