// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// TODO(findleyr): update JSON marshalling of all content types to preserve required fields.
// (See [TextContent.MarshalJSON], which handles this for text content).

package mcp

import (
	"encoding/json"
	"fmt"

	internaljson "github.com/modelcontextprotocol/go-sdk/internal/json"
)

// A Content is a [TextContent], [ImageContent], [AudioContent],
// [ResourceLink], [EmbeddedResource], [ToolUseContent], or [ToolResultContent].
//
// Note: [ToolUseContent] and [ToolResultContent] are only valid in sampling
// message contexts (CreateMessageParams/CreateMessageResult).
type Content interface {
	MarshalJSON() ([]byte, error)
	fromWire(*wireContent)
}

// TextContent is a textual content.
type TextContent struct {
	Text        string
	Meta        Meta
	Annotations *Annotations
}

func (c *TextContent) MarshalJSON() ([]byte, error) {
	// Custom wire format to ensure the required "text" field is always included, even when empty.
	wire := struct {
		Type        string       `json:"type"`
		Text        string       `json:"text"`
		Meta        Meta         `json:"_meta,omitempty"`
		Annotations *Annotations `json:"annotations,omitempty"`
	}{
		Type:        "text",
		Text:        c.Text,
		Meta:        c.Meta,
		Annotations: c.Annotations,
	}
	return json.Marshal(wire)
}

func (c *TextContent) fromWire(wire *wireContent) {
	c.Text = wire.Text
	c.Meta = wire.Meta
	c.Annotations = wire.Annotations
}

// ImageContent contains base64-encoded image data.
type ImageContent struct {
	Meta        Meta
	Annotations *Annotations
	Data        []byte // base64-encoded
	MIMEType    string
}

func (c *ImageContent) MarshalJSON() ([]byte, error) {
	// Custom wire format to ensure required fields are always included, even when empty.
	data := c.Data
	if data == nil {
		data = []byte{}
	}
	wire := imageAudioWire{
		Type:        "image",
		MIMEType:    c.MIMEType,
		Data:        data,
		Meta:        c.Meta,
		Annotations: c.Annotations,
	}
	return json.Marshal(wire)
}

func (c *ImageContent) fromWire(wire *wireContent) {
	c.MIMEType = wire.MIMEType
	c.Data = wire.Data
	c.Meta = wire.Meta
	c.Annotations = wire.Annotations
}

// AudioContent contains base64-encoded audio data.
type AudioContent struct {
	Data        []byte
	MIMEType    string
	Meta        Meta
	Annotations *Annotations
}

func (c AudioContent) MarshalJSON() ([]byte, error) {
	// Custom wire format to ensure required fields are always included, even when empty.
	data := c.Data
	if data == nil {
		data = []byte{}
	}
	wire := imageAudioWire{
		Type:        "audio",
		MIMEType:    c.MIMEType,
		Data:        data,
		Meta:        c.Meta,
		Annotations: c.Annotations,
	}
	return json.Marshal(wire)
}

func (c *AudioContent) fromWire(wire *wireContent) {
	c.MIMEType = wire.MIMEType
	c.Data = wire.Data
	c.Meta = wire.Meta
	c.Annotations = wire.Annotations
}

// Custom wire format to ensure required fields are always included, even when empty.
type imageAudioWire struct {
	Type        string       `json:"type"`
	MIMEType    string       `json:"mimeType"`
	Data        []byte       `json:"data"`
	Meta        Meta         `json:"_meta,omitempty"`
	Annotations *Annotations `json:"annotations,omitempty"`
}

// ResourceLink is a link to a resource
type ResourceLink struct {
	URI         string
	Name        string
	Title       string
	Description string
	MIMEType    string
	Size        *int64
	Meta        Meta
	Annotations *Annotations
	// Icons for the resource link, if any.
	Icons []Icon `json:"icons,omitempty"`
}

func (c *ResourceLink) MarshalJSON() ([]byte, error) {
	return json.Marshal(&wireContent{
		Type:        "resource_link",
		URI:         c.URI,
		Name:        c.Name,
		Title:       c.Title,
		Description: c.Description,
		MIMEType:    c.MIMEType,
		Size:        c.Size,
		Meta:        c.Meta,
		Annotations: c.Annotations,
		Icons:       c.Icons,
	})
}

func (c *ResourceLink) fromWire(wire *wireContent) {
	c.URI = wire.URI
	c.Name = wire.Name
	c.Title = wire.Title
	c.Description = wire.Description
	c.MIMEType = wire.MIMEType
	c.Size = wire.Size
	c.Meta = wire.Meta
	c.Annotations = wire.Annotations
	c.Icons = wire.Icons
}

// EmbeddedResource contains embedded resources.
type EmbeddedResource struct {
	Resource    *ResourceContents
	Meta        Meta
	Annotations *Annotations
}

func (c *EmbeddedResource) MarshalJSON() ([]byte, error) {
	return json.Marshal(&wireContent{
		Type:        "resource",
		Resource:    c.Resource,
		Meta:        c.Meta,
		Annotations: c.Annotations,
	})
}

func (c *EmbeddedResource) fromWire(wire *wireContent) {
	c.Resource = wire.Resource
	c.Meta = wire.Meta
	c.Annotations = wire.Annotations
}

// ToolUseContent represents a request from the assistant to invoke a tool.
// This content type is only valid in sampling messages.
type ToolUseContent struct {
	// ID is a unique identifier for this tool use, used to match with ToolResultContent.
	ID string
	// Name is the name of the tool to invoke.
	Name string
	// Input contains the tool arguments as a JSON object.
	Input map[string]any
	Meta  Meta
}

func (c *ToolUseContent) MarshalJSON() ([]byte, error) {
	input := c.Input
	if input == nil {
		input = map[string]any{}
	}
	wire := struct {
		Type  string         `json:"type"`
		ID    string         `json:"id"`
		Name  string         `json:"name"`
		Input map[string]any `json:"input"`
		Meta  Meta           `json:"_meta,omitempty"`
	}{
		Type:  "tool_use",
		ID:    c.ID,
		Name:  c.Name,
		Input: input,
		Meta:  c.Meta,
	}
	return json.Marshal(wire)
}

func (c *ToolUseContent) fromWire(wire *wireContent) {
	c.ID = wire.ID
	c.Name = wire.Name
	c.Input = wire.Input
	c.Meta = wire.Meta
}

// ToolResultContent represents the result of a tool invocation.
// This content type is only valid in sampling messages with role "user".
type ToolResultContent struct {
	// ToolUseID references the ID from the corresponding ToolUseContent.
	ToolUseID string
	// Content holds the unstructured result of the tool call.
	Content []Content
	// StructuredContent holds an optional structured result as a JSON object.
	StructuredContent any
	// IsError indicates whether the tool call ended in an error.
	IsError bool
	Meta    Meta
}

func (c *ToolResultContent) MarshalJSON() ([]byte, error) {
	// Marshal nested content
	var contentWire []*wireContent
	for _, content := range c.Content {
		data, err := content.MarshalJSON()
		if err != nil {
			return nil, err
		}
		var w wireContent
		if err := internaljson.Unmarshal(data, &w); err != nil {
			return nil, err
		}
		contentWire = append(contentWire, &w)
	}
	if contentWire == nil {
		contentWire = []*wireContent{} // avoid JSON null
	}

	wire := struct {
		Type              string         `json:"type"`
		ToolUseID         string         `json:"toolUseId"`
		Content           []*wireContent `json:"content"`
		StructuredContent any            `json:"structuredContent,omitempty"`
		IsError           bool           `json:"isError,omitempty"`
		Meta              Meta           `json:"_meta,omitempty"`
	}{
		Type:              "tool_result",
		ToolUseID:         c.ToolUseID,
		Content:           contentWire,
		StructuredContent: c.StructuredContent,
		IsError:           c.IsError,
		Meta:              c.Meta,
	}
	return json.Marshal(wire)
}

func (c *ToolResultContent) fromWire(wire *wireContent) {
	c.ToolUseID = wire.ToolUseID
	c.StructuredContent = wire.StructuredContent
	c.IsError = wire.IsError
	c.Meta = wire.Meta
	// Content is handled separately in contentFromWire due to nested content
}

// ResourceContents contains the contents of a specific resource or
// sub-resource.
type ResourceContents struct {
	URI      string `json:"uri"`
	MIMEType string `json:"mimeType,omitempty"`
	Text     string `json:"text,omitempty"`
	Blob     []byte `json:"blob,omitzero"`
	Meta     Meta   `json:"_meta,omitempty"`
}

// wireContent is the wire format for content.
// It represents the protocol types TextContent, ImageContent, AudioContent,
// ResourceLink, EmbeddedResource, ToolUseContent, and ToolResultContent.
// The Type field distinguishes them. In the protocol, each type has a constant
// value for the field.
type wireContent struct {
	Type              string            `json:"type"`
	Text              string            `json:"text,omitempty"`              // TextContent
	MIMEType          string            `json:"mimeType,omitempty"`          // ImageContent, AudioContent, ResourceLink
	Data              []byte            `json:"data,omitempty"`              // ImageContent, AudioContent
	Resource          *ResourceContents `json:"resource,omitempty"`          // EmbeddedResource
	URI               string            `json:"uri,omitempty"`               // ResourceLink
	Name              string            `json:"name,omitempty"`              // ResourceLink, ToolUseContent
	Title             string            `json:"title,omitempty"`             // ResourceLink
	Description       string            `json:"description,omitempty"`       // ResourceLink
	Size              *int64            `json:"size,omitempty"`              // ResourceLink
	Meta              Meta              `json:"_meta,omitempty"`             // all types
	Annotations       *Annotations      `json:"annotations,omitempty"`       // all types except ToolUseContent, ToolResultContent
	Icons             []Icon            `json:"icons,omitempty"`             // ResourceLink
	ID                string            `json:"id,omitempty"`                // ToolUseContent
	Input             map[string]any    `json:"input,omitempty"`             // ToolUseContent
	ToolUseID         string            `json:"toolUseId,omitempty"`         // ToolResultContent
	NestedContent     []*wireContent    `json:"content,omitempty"`           // ToolResultContent
	StructuredContent any               `json:"structuredContent,omitempty"` // ToolResultContent
	IsError           bool              `json:"isError,omitempty"`           // ToolResultContent
}

// unmarshalContent unmarshals JSON that is either a single content object or
// an array of content objects. A single object is wrapped in a one-element slice.
func unmarshalContent(raw json.RawMessage, allow map[string]bool) ([]Content, error) {
	if len(raw) == 0 || string(raw) == "null" {
		return nil, fmt.Errorf("nil content")
	}
	// Try array first, then fall back to single object.
	var wires []*wireContent
	if err := internaljson.Unmarshal(raw, &wires); err == nil {
		return contentsFromWire(wires, allow)
	}
	var wire wireContent
	if err := internaljson.Unmarshal(raw, &wire); err != nil {
		return nil, err
	}
	c, err := contentFromWire(&wire, allow)
	if err != nil {
		return nil, err
	}
	return []Content{c}, nil
}

func contentsFromWire(wires []*wireContent, allow map[string]bool) ([]Content, error) {
	blocks := make([]Content, 0, len(wires))
	for _, wire := range wires {
		block, err := contentFromWire(wire, allow)
		if err != nil {
			return nil, err
		}
		blocks = append(blocks, block)
	}
	return blocks, nil
}

func contentFromWire(wire *wireContent, allow map[string]bool) (Content, error) {
	if wire == nil {
		return nil, fmt.Errorf("nil content")
	}
	if allow != nil && !allow[wire.Type] {
		return nil, fmt.Errorf("invalid content type %q", wire.Type)
	}
	switch wire.Type {
	case "text":
		v := new(TextContent)
		v.fromWire(wire)
		return v, nil
	case "image":
		v := new(ImageContent)
		v.fromWire(wire)
		return v, nil
	case "audio":
		v := new(AudioContent)
		v.fromWire(wire)
		return v, nil
	case "resource_link":
		v := new(ResourceLink)
		v.fromWire(wire)
		return v, nil
	case "resource":
		v := new(EmbeddedResource)
		v.fromWire(wire)
		return v, nil
	case "tool_use":
		v := new(ToolUseContent)
		v.fromWire(wire)
		return v, nil
	case "tool_result":
		v := new(ToolResultContent)
		v.fromWire(wire)
		// Handle nested content - tool_result content can contain text, image, audio,
		// resource_link, and resource (same as CallToolResult.content)
		if wire.NestedContent != nil {
			toolResultContentAllow := map[string]bool{
				"text": true, "image": true, "audio": true,
				"resource_link": true, "resource": true,
			}
			nestedContent, err := contentsFromWire(wire.NestedContent, toolResultContentAllow)
			if err != nil {
				return nil, fmt.Errorf("tool_result nested content: %w", err)
			}
			v.Content = nestedContent
		}
		return v, nil
	}
	return nil, fmt.Errorf("unrecognized content type %q", wire.Type)
}
