package openai

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

// logicalRequestEndpoint keeps fingerprints from incompatible wire formats
// distinct even when their JSON payloads happen to look alike.
type logicalRequestEndpoint string

const (
	logicalRequestEndpointChat      logicalRequestEndpoint = "chat_completions"
	logicalRequestEndpointAnthropic logicalRequestEndpoint = "anthropic_messages"
)

// logicalRequestLineage is the immutable request prefix and tool contract
// recorded when a logical request is admitted. It intentionally contains only
// digests: raw prompts, tool schemas, and tool results never enter the store.
type logicalRequestLineage struct {
	Endpoint      logicalRequestEndpoint
	HistoryDigest string
	ToolsetDigest string
}

type logicalRequestContinuationLineage struct {
	Prefix         logicalRequestLineage
	IssuedCallHash string
	ResultIDs      []string
	Committed      logicalRequestLineage
}

func newChatRequestLineage(raw json.RawMessage) (logicalRequestLineage, error) {
	fields, err := decodeLogicalRequestLineageEnvelope(raw)
	if err != nil {
		return logicalRequestLineage{}, err
	}
	rawMessages, ok := fields["messages"]
	if !ok {
		return logicalRequestLineage{}, fmt.Errorf("chat messages field is required")
	}
	if _, err := validateChatMessages(rawMessages); err != nil {
		return logicalRequestLineage{}, err
	}
	return newLogicalRequestLineageFromRawFields(fields, logicalRequestEndpointChat, []string{"model", "messages"})
}

func newAnthropicRequestLineage(raw json.RawMessage) (logicalRequestLineage, error) {
	fields, err := decodeLogicalRequestLineageEnvelope(raw)
	if err != nil {
		return logicalRequestLineage{}, err
	}
	rawMessages, ok := fields["messages"]
	if !ok {
		return logicalRequestLineage{}, fmt.Errorf("anthropic messages field is required")
	}
	if _, err := validateAnthropicMessages(rawMessages); err != nil {
		return logicalRequestLineage{}, err
	}
	return newLogicalRequestLineageFromRawFields(fields, logicalRequestEndpointAnthropic, []string{"model", "system", "messages"})
}

type chatMessageValidation struct {
	Role       string `json:"role"`
	ToolCallID string `json:"tool_call_id"`
	ToolCalls  []struct {
		ID string `json:"id"`
	} `json:"tool_calls"`
}

func validateChatMessages(rawMessages json.RawMessage) ([]json.RawMessage, error) {
	if len(rawMessages) == 0 {
		return nil, fmt.Errorf("chat messages field is required")
	}
	var msgList []json.RawMessage
	decoder := json.NewDecoder(bytes.NewReader(rawMessages))
	decoder.UseNumber()
	if err := decoder.Decode(&msgList); err != nil {
		return nil, fmt.Errorf("chat messages must be an array: %w", err)
	}
	if len(msgList) == 0 {
		return nil, fmt.Errorf("chat messages array must not be empty")
	}
	validRoles := map[string]struct{}{
		"system":    {},
		"developer": {},
		"user":      {},
		"assistant": {},
		"tool":      {},
	}

	globallySeenIssuedIDs := make(map[string]struct{})
	pendingToolCallIDs := make(map[string]struct{})

	for i, rawMsg := range msgList {
		var m chatMessageValidation
		if err := json.Unmarshal(rawMsg, &m); err != nil {
			return nil, fmt.Errorf("decode chat message at index %d: %w", i, err)
		}
		if _, ok := validRoles[m.Role]; !ok {
			return nil, fmt.Errorf("unknown chat message role %q at index %d", m.Role, i)
		}

		if m.Role == "tool" {
			if len(pendingToolCallIDs) == 0 {
				return nil, fmt.Errorf("orphan tool result message at index %d", i)
			}
			if m.ToolCallID == "" {
				return nil, fmt.Errorf("tool message at index %d has empty tool_call_id", i)
			}
			if _, ok := pendingToolCallIDs[m.ToolCallID]; !ok {
				return nil, fmt.Errorf("tool message at index %d has unexpected or duplicate tool_call_id %q", i, m.ToolCallID)
			}
			delete(pendingToolCallIDs, m.ToolCallID)
		} else {
			if len(pendingToolCallIDs) > 0 {
				return nil, fmt.Errorf("message at index %d with role %q appeared before all preceding tool_calls were satisfied", i, m.Role)
			}

			if m.Role == "assistant" && len(m.ToolCalls) > 0 {
				for tcIdx, tc := range m.ToolCalls {
					if tc.ID == "" {
						return nil, fmt.Errorf("assistant message at index %d tool call %d has empty id", i, tcIdx)
					}
					if _, duplicate := globallySeenIssuedIDs[tc.ID]; duplicate {
						return nil, fmt.Errorf("duplicate issued assistant tool call id %q at index %d", tc.ID, i)
					}
					globallySeenIssuedIDs[tc.ID] = struct{}{}
					pendingToolCallIDs[tc.ID] = struct{}{}
				}
			}
		}
	}

	if len(pendingToolCallIDs) > 0 {
		return nil, fmt.Errorf("message list ended before all assistant tool_calls were satisfied")
	}

	return msgList, nil
}

func validateAnthropicMessages(rawMessages json.RawMessage) ([]json.RawMessage, error) {
	if len(rawMessages) == 0 {
		return nil, fmt.Errorf("anthropic messages field is required")
	}
	var msgList []json.RawMessage
	decoder := json.NewDecoder(bytes.NewReader(rawMessages))
	decoder.UseNumber()
	if err := decoder.Decode(&msgList); err != nil {
		return nil, fmt.Errorf("anthropic messages must be an array: %w", err)
	}
	if len(msgList) == 0 {
		return nil, fmt.Errorf("anthropic messages array must not be empty")
	}

	globallySeenToolUseIDs := make(map[string]struct{})
	pendingToolUseIDs := make(map[string]struct{})

	for i, rawMsg := range msgList {
		var m struct {
			Role    string          `json:"role"`
			Content json.RawMessage `json:"content"`
		}
		if err := json.Unmarshal(rawMsg, &m); err != nil {
			return nil, fmt.Errorf("decode anthropic message at index %d: %w", i, err)
		}
		if m.Role != "user" && m.Role != "assistant" && m.Role != "system" {
			return nil, fmt.Errorf("invalid anthropic message role %q at index %d", m.Role, i)
		}
		if i == 0 && m.Role != "user" {
			return nil, fmt.Errorf("anthropic messages first message must have role user, got %q", m.Role)
		}
		if m.Role == "system" {
			if len(pendingToolUseIDs) > 0 {
				return nil, fmt.Errorf("anthropic system message at index %d cannot interrupt pending tool results", i)
			}
			if i > 0 {
				var previous struct {
					Role string `json:"role"`
				}
				_ = json.Unmarshal(msgList[i-1], &previous)
				if previous.Role != "user" && previous.Role != "system" {
					return nil, fmt.Errorf("anthropic system message at index %d must follow a user message", i)
				}
			}
			if i+1 < len(msgList) {
				var next struct {
					Role string `json:"role"`
				}
				_ = json.Unmarshal(msgList[i+1], &next)
				if next.Role != "assistant" && next.Role != "system" {
					return nil, fmt.Errorf("anthropic system message at index %d must precede an assistant message or end the message list", i)
				}
			}
		} else if i > 0 {
			var prev struct {
				Role string `json:"role"`
			}
			for previousIndex := i - 1; previousIndex >= 0; previousIndex-- {
				_ = json.Unmarshal(msgList[previousIndex], &prev)
				if prev.Role != "system" {
					break
				}
			}
			if m.Role == prev.Role {
				return nil, fmt.Errorf("anthropic messages roles must alternate, repeated role %q at index %d", m.Role, i)
			}
		}

		blocks, err := decodeAnthropicContent(m.Content)
		if err != nil {
			return nil, fmt.Errorf("anthropic message %d: %w", i, err)
		}

		if m.Role == "system" {
			for bIdx, block := range blocks {
				if block.Type != "text" {
					return nil, fmt.Errorf("anthropic system message %d block %d has invalid type %q", i, bIdx, block.Type)
				}
			}
		} else if m.Role == "assistant" {
			for bIdx, block := range blocks {
				if block.Type == "tool_result" || block.Type == "image" {
					return nil, fmt.Errorf("anthropic assistant message %d block %d has invalid type %q", i, bIdx, block.Type)
				}
				if block.Type == "tool_use" {
					if block.ID == "" {
						return nil, fmt.Errorf("anthropic assistant message %d tool_use block %d has empty id", i, bIdx)
					}
					if _, duplicate := globallySeenToolUseIDs[block.ID]; duplicate {
						return nil, fmt.Errorf("duplicate issued assistant tool_use id %q at message %d", block.ID, i)
					}
					globallySeenToolUseIDs[block.ID] = struct{}{}
					pendingToolUseIDs[block.ID] = struct{}{}
				}
			}
		} else if m.Role == "user" {
			if len(pendingToolUseIDs) > 0 {
				if len(blocks) != len(pendingToolUseIDs) {
					return nil, fmt.Errorf("anthropic user message %d tool results count (%d) does not match issued tool_use count (%d)", i, len(blocks), len(pendingToolUseIDs))
				}
				for bIdx, block := range blocks {
					if block.Type != "tool_result" {
						return nil, fmt.Errorf("anthropic user message %d block %d has non-tool_result type %q when responding to tool_use", i, bIdx, block.Type)
					}
					if block.ToolUseID == "" {
						return nil, fmt.Errorf("anthropic user message %d tool_result block %d missing tool_use_id", i, bIdx)
					}
					if _, ok := pendingToolUseIDs[block.ToolUseID]; !ok {
						return nil, fmt.Errorf("anthropic user message %d tool_result tool_use_id %q not in issued tool_use blocks or duplicate", i, block.ToolUseID)
					}
					delete(pendingToolUseIDs, block.ToolUseID)
				}
			} else {
				for bIdx, block := range blocks {
					if block.Type == "tool_use" || block.Type == "thinking" {
						return nil, fmt.Errorf("anthropic user message %d block %d has invalid type %q", i, bIdx, block.Type)
					}
					if block.Type == "tool_result" {
						return nil, fmt.Errorf("orphan tool_result block in anthropic user message at index %d", i)
					}
				}
			}
		}
	}

	if len(pendingToolUseIDs) > 0 {
		return nil, fmt.Errorf("anthropic message list ended before tool_use blocks were satisfied")
	}

	return msgList, nil
}

func newChatContinuationLineage(raw json.RawMessage) (logicalRequestContinuationLineage, error) {
	fields, err := decodeLogicalRequestLineageEnvelope(raw)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	rawMessages, ok := fields["messages"]
	if !ok {
		return logicalRequestContinuationLineage{}, fmt.Errorf("chat continuation messages field is required")
	}

	msgList, err := validateChatMessages(rawMessages)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}

	var resultIDs []string
	seenResultIDs := make(map[string]struct{})
	resultCount := 0

	for i := len(msgList) - 1; i >= 0; i-- {
		var msg struct {
			Role       string `json:"role"`
			ToolCallID string `json:"tool_call_id"`
		}
		if err := json.Unmarshal(msgList[i], &msg); err != nil {
			return logicalRequestContinuationLineage{}, fmt.Errorf("decode message at index %d: %w", i, err)
		}
		if msg.Role == "tool" {
			if msg.ToolCallID == "" {
				return logicalRequestContinuationLineage{}, fmt.Errorf("tool message at index %d has empty tool_call_id", i)
			}
			if _, exists := seenResultIDs[msg.ToolCallID]; exists {
				return logicalRequestContinuationLineage{}, fmt.Errorf("duplicate tool_call_id %q in frontier", msg.ToolCallID)
			}
			seenResultIDs[msg.ToolCallID] = struct{}{}
			resultIDs = append([]string{msg.ToolCallID}, resultIDs...)
			resultCount++
		} else {
			break
		}
	}

	if resultCount == 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("chat continuation must end with at least one tool result message")
	}

	assistantIndex := len(msgList) - resultCount - 1
	if assistantIndex < 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("chat continuation missing issued assistant message before tool results")
	}

	var assistantMsg struct {
		Role      string `json:"role"`
		ToolCalls []struct {
			ID string `json:"id"`
		} `json:"tool_calls"`
	}
	if err := json.Unmarshal(msgList[assistantIndex], &assistantMsg); err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("decode assistant message: %w", err)
	}
	if assistantMsg.Role != "assistant" {
		return logicalRequestContinuationLineage{}, fmt.Errorf("expected assistant message before tool results, got role %q", assistantMsg.Role)
	}
	if len(assistantMsg.ToolCalls) == 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("issued assistant message must contain tool_calls")
	}

	expectedToolCallIDs := make(map[string]struct{}, len(assistantMsg.ToolCalls))
	for _, tc := range assistantMsg.ToolCalls {
		if tc.ID == "" {
			return logicalRequestContinuationLineage{}, fmt.Errorf("issued assistant tool call has empty id")
		}
		if _, duplicate := expectedToolCallIDs[tc.ID]; duplicate {
			return logicalRequestContinuationLineage{}, fmt.Errorf("duplicate issued assistant tool call id %q", tc.ID)
		}
		expectedToolCallIDs[tc.ID] = struct{}{}
	}
	if len(expectedToolCallIDs) != len(seenResultIDs) {
		return logicalRequestContinuationLineage{}, fmt.Errorf("frontier tool results count (%d) does not match issued assistant tool_calls count (%d)", len(seenResultIDs), len(expectedToolCallIDs))
	}
	for id := range seenResultIDs {
		if _, ok := expectedToolCallIDs[id]; !ok {
			return logicalRequestContinuationLineage{}, fmt.Errorf("frontier tool_call_id %q not in issued assistant tool_calls", id)
		}
	}

	issuedCallHash, err := chatIssuedCallHash(msgList[assistantIndex])
	if err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("fingerprint issued assistant call: %w", err)
	}

	prefixMessagesRaw, err := json.Marshal(msgList[:assistantIndex])
	if err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("marshal prefix messages: %w", err)
	}

	prefixHistory := map[string]json.RawMessage{
		"model":    fields["model"],
		"messages": prefixMessagesRaw,
	}
	prefixHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointChat, prefixHistory)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	toolsetDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointChat, fields["tools"])
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	prefixLineage := logicalRequestLineage{
		Endpoint:      logicalRequestEndpointChat,
		HistoryDigest: prefixHistoryDigest,
		ToolsetDigest: toolsetDigest,
	}

	committedHistory := map[string]json.RawMessage{
		"model":    fields["model"],
		"messages": fields["messages"],
	}
	committedHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointChat, committedHistory)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	committedLineage := logicalRequestLineage{
		Endpoint:      logicalRequestEndpointChat,
		HistoryDigest: committedHistoryDigest,
		ToolsetDigest: toolsetDigest,
	}

	return logicalRequestContinuationLineage{
		Prefix:         prefixLineage,
		IssuedCallHash: issuedCallHash,
		ResultIDs:      resultIDs,
		Committed:      committedLineage,
	}, nil
}

func newAnthropicContinuationLineage(raw json.RawMessage) (logicalRequestContinuationLineage, error) {
	fields, err := decodeLogicalRequestLineageEnvelope(raw)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	rawMessages, ok := fields["messages"]
	if !ok {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation messages field is required")
	}

	msgList, err := validateAnthropicMessages(rawMessages)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}

	lastIndex := len(msgList) - 1
	var lastMsg struct {
		Role    string          `json:"role"`
		Content json.RawMessage `json:"content"`
	}
	if err := json.Unmarshal(msgList[lastIndex], &lastMsg); err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("decode last anthropic message: %w", err)
	}
	if lastMsg.Role != "user" {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation last message must have role user, got %q", lastMsg.Role)
	}

	var blocks []json.RawMessage
	if err := json.Unmarshal(lastMsg.Content, &blocks); err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation user message content must be array of blocks: %w", err)
	}

	var resultIDs []string
	seenResultIDs := make(map[string]struct{})
	for bIdx, blockRaw := range blocks {
		var block struct {
			Type      string `json:"type"`
			ToolUseID string `json:"tool_use_id"`
		}
		if err := json.Unmarshal(blockRaw, &block); err != nil {
			return logicalRequestContinuationLineage{}, fmt.Errorf("decode content block %d: %w", bIdx, err)
		}
		if block.Type != "tool_result" {
			return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation trailing user message block %d has non-tool_result type %q", bIdx, block.Type)
		}
		if block.ToolUseID == "" {
			return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic tool_result block %d missing tool_use_id", bIdx)
		}
		if _, exists := seenResultIDs[block.ToolUseID]; exists {
			return logicalRequestContinuationLineage{}, fmt.Errorf("duplicate tool_use_id %q in anthropic frontier", block.ToolUseID)
		}
		seenResultIDs[block.ToolUseID] = struct{}{}
		resultIDs = append(resultIDs, block.ToolUseID)
	}
	if len(resultIDs) == 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation trailing user message contains no tool_result blocks")
	}

	assistantIndex := lastIndex - 1
	if assistantIndex < 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic continuation missing issued assistant message before tool results")
	}

	var assistantMsg struct {
		Role    string          `json:"role"`
		Content json.RawMessage `json:"content"`
	}
	if err := json.Unmarshal(msgList[assistantIndex], &assistantMsg); err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("decode assistant message: %w", err)
	}
	if assistantMsg.Role != "assistant" {
		return logicalRequestContinuationLineage{}, fmt.Errorf("expected assistant message before tool results, got role %q", assistantMsg.Role)
	}

	var assistantBlocks []json.RawMessage
	if err := json.Unmarshal(assistantMsg.Content, &assistantBlocks); err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("assistant message content must be array of blocks: %w", err)
	}

	expectedToolUseIDs := make(map[string]struct{})
	for bIdx, blockRaw := range assistantBlocks {
		var block struct {
			Type string `json:"type"`
			ID   string `json:"id"`
		}
		if err := json.Unmarshal(blockRaw, &block); err != nil {
			return logicalRequestContinuationLineage{}, fmt.Errorf("decode assistant content block %d: %w", bIdx, err)
		}
		if block.Type == "tool_use" {
			if block.ID == "" {
				return logicalRequestContinuationLineage{}, fmt.Errorf("issued assistant tool_use block has empty id")
			}
			if _, duplicate := expectedToolUseIDs[block.ID]; duplicate {
				return logicalRequestContinuationLineage{}, fmt.Errorf("duplicate issued assistant tool_use id %q", block.ID)
			}
			expectedToolUseIDs[block.ID] = struct{}{}
		}
	}
	if len(expectedToolUseIDs) == 0 {
		return logicalRequestContinuationLineage{}, fmt.Errorf("issued assistant message contains no tool_use blocks")
	}
	if len(expectedToolUseIDs) != len(seenResultIDs) {
		return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic frontier tool results count (%d) does not match issued tool_use count (%d)", len(seenResultIDs), len(expectedToolUseIDs))
	}
	for id := range seenResultIDs {
		if _, ok := expectedToolUseIDs[id]; !ok {
			return logicalRequestContinuationLineage{}, fmt.Errorf("anthropic tool_result tool_use_id %q not in issued assistant tool_use blocks", id)
		}
	}

	issuedCallHash, err := anthropicIssuedCallHash(msgList[assistantIndex])
	if err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("fingerprint issued assistant call: %w", err)
	}

	prefixMessagesRaw, err := json.Marshal(msgList[:assistantIndex])
	if err != nil {
		return logicalRequestContinuationLineage{}, fmt.Errorf("marshal prefix messages: %w", err)
	}

	prefixHistory := map[string]json.RawMessage{
		"model":    fields["model"],
		"system":   fields["system"],
		"messages": prefixMessagesRaw,
	}
	prefixHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, prefixHistory)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	toolsetDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, fields["tools"])
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	prefixLineage := logicalRequestLineage{
		Endpoint:      logicalRequestEndpointAnthropic,
		HistoryDigest: prefixHistoryDigest,
		ToolsetDigest: toolsetDigest,
	}

	committedHistory := map[string]json.RawMessage{
		"model":    fields["model"],
		"system":   fields["system"],
		"messages": fields["messages"],
	}
	committedHistoryDigest, err := fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, committedHistory)
	if err != nil {
		return logicalRequestContinuationLineage{}, err
	}
	committedLineage := logicalRequestLineage{
		Endpoint:      logicalRequestEndpointAnthropic,
		HistoryDigest: committedHistoryDigest,
		ToolsetDigest: toolsetDigest,
	}

	return logicalRequestContinuationLineage{
		Prefix:         prefixLineage,
		IssuedCallHash: issuedCallHash,
		ResultIDs:      resultIDs,
		Committed:      committedLineage,
	}, nil
}

func chatIssuedCallHash(raw json.RawMessage) (string, error) {
	var message struct {
		Role      string `json:"role"`
		ToolCalls []struct {
			ID       string `json:"id"`
			Type     string `json:"type"`
			Function struct {
				Name      string `json:"name"`
				Arguments string `json:"arguments"`
			} `json:"function"`
		} `json:"tool_calls"`
	}
	if err := json.Unmarshal(raw, &message); err != nil {
		return "", err
	}
	semanticCalls := make([]map[string]any, 0, len(message.ToolCalls))
	for _, call := range message.ToolCalls {
		var arguments any = map[string]any{}
		if strings.TrimSpace(call.Function.Arguments) != "" {
			decoder := json.NewDecoder(bytes.NewBufferString(call.Function.Arguments))
			decoder.UseNumber()
			if err := decoder.Decode(&arguments); err != nil {
				return "", fmt.Errorf("decode issued tool arguments: %w", err)
			}
		}
		semanticCalls = append(semanticCalls, map[string]any{
			"id": call.ID, "type": call.Type,
			"function": map[string]any{"name": call.Function.Name, "arguments": arguments},
		})
	}
	return fingerprintCanonicalJSON(logicalRequestEndpointChat, map[string]any{
		"role": message.Role, "tool_calls": semanticCalls,
	})
}

func anthropicIssuedCallHash(raw json.RawMessage) (string, error) {
	var message struct {
		Role    string            `json:"role"`
		Content []json.RawMessage `json:"content"`
	}
	if err := json.Unmarshal(raw, &message); err != nil {
		return "", err
	}
	type semanticToolUse struct {
		Type  string          `json:"type"`
		ID    string          `json:"id"`
		Name  string          `json:"name"`
		Input json.RawMessage `json:"input"`
	}
	toolUses := make([]semanticToolUse, 0, len(message.Content))
	for _, block := range message.Content {
		var toolUse semanticToolUse
		if err := json.Unmarshal(block, &toolUse); err != nil {
			return "", err
		}
		if toolUse.Type == "tool_use" {
			toolUses = append(toolUses, toolUse)
		}
	}
	return fingerprintCanonicalJSON(logicalRequestEndpointAnthropic, map[string]any{
		"role": message.Role, "content": toolUses,
	})
}

func newLogicalRequestLineageFromRaw(raw json.RawMessage, endpoint logicalRequestEndpoint, historyFields []string) (logicalRequestLineage, error) {
	fields, err := decodeLogicalRequestLineageEnvelope(raw)
	if err != nil {
		return logicalRequestLineage{}, err
	}
	return newLogicalRequestLineageFromRawFields(fields, endpoint, historyFields)
}

func newLogicalRequestLineageFromRawFields(fields map[string]json.RawMessage, endpoint logicalRequestEndpoint, historyFields []string) (logicalRequestLineage, error) {
	history := make(map[string]json.RawMessage, len(historyFields))
	for _, field := range historyFields {
		history[field] = fields[field]
	}
	historyDigest, err := fingerprintCanonicalJSON(endpoint, history)
	if err != nil {
		return logicalRequestLineage{}, err
	}
	toolsetDigest, err := fingerprintCanonicalJSON(endpoint, fields["tools"])
	if err != nil {
		return logicalRequestLineage{}, err
	}
	return logicalRequestLineage{Endpoint: endpoint, HistoryDigest: historyDigest, ToolsetDigest: toolsetDigest}, nil
}

func decodeLogicalRequestLineageEnvelope(raw json.RawMessage) (map[string]json.RawMessage, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var fields map[string]json.RawMessage
	if err := decoder.Decode(&fields); err != nil {
		return nil, fmt.Errorf("decode logical request lineage envelope: %w", err)
	}
	if fields == nil {
		return nil, fmt.Errorf("logical request lineage envelope must be an object")
	}
	var extra any
	if err := decoder.Decode(&extra); err == nil {
		return nil, fmt.Errorf("logical request lineage envelope contains multiple JSON values")
	} else if err != io.EOF {
		return nil, fmt.Errorf("decode logical request lineage envelope: %w", err)
	}
	return fields, nil
}

// fingerprintCanonicalJSON normalizes nested JSON before hashing. Decoding
// RawMessage values first prevents insignificant formatting differences in a
// caller's schema or Anthropic content blocks from becoming new lineages.
func fingerprintCanonicalJSON(endpoint logicalRequestEndpoint, value any) (string, error) {
	raw, err := json.Marshal(value)
	if err != nil {
		return "", fmt.Errorf("marshal logical request lineage: %w", err)
	}
	var canonical any
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	if err := decoder.Decode(&canonical); err != nil {
		return "", fmt.Errorf("decode logical request lineage: %w", err)
	}
	normalized, err := json.Marshal(canonical)
	if err != nil {
		return "", fmt.Errorf("encode logical request lineage: %w", err)
	}
	sum := sha256.Sum256(append(append([]byte(endpoint), '\n'), normalized...))
	return hex.EncodeToString(sum[:]), nil
}
