package kit

import (
	"context"

	"charm.land/fantasy"

	"github.com/mark3labs/kit/internal/agent"
	"github.com/mark3labs/kit/internal/compaction"
	"github.com/mark3labs/kit/internal/config"
	"github.com/mark3labs/kit/internal/message"
	"github.com/mark3labs/kit/internal/models"
	"github.com/mark3labs/kit/internal/session"
	"github.com/mark3labs/kit/internal/tools"
	"github.com/mark3labs/mcp-go/client/transport"
	"github.com/mark3labs/mcp-go/server"
)

// ==== Message Types (internal/message/content.go) ====

// Message is a single conversation message containing heterogeneous content
// parts (text, reasoning, tool calls, tool results, finish markers).
type Message = message.Message

// MessageRole identifies the sender of a message (user, assistant, tool, system).
type MessageRole = message.MessageRole

const (
	RoleUser      = message.RoleUser
	RoleAssistant = message.RoleAssistant
	RoleTool      = message.RoleTool
	RoleSystem    = message.RoleSystem
)

// ContentPart is the marker interface for all message content block types.
type ContentPart = message.ContentPart

// TextContent holds plain text content within a message.
type TextContent = message.TextContent

// ReasoningContent holds extended thinking / reasoning output from the LLM.
type ReasoningContent = message.ReasoningContent

// ToolCall represents a tool invocation initiated by the LLM.
type ToolCall = message.ToolCall

// ToolResult represents the result of executing a tool.
type ToolResult = message.ToolResult

// Finish marks the end of an assistant turn, carrying the stop reason.
type Finish = message.Finish

// ==== Session Types (internal/session/) ====

// SessionInfo contains metadata about a discovered session, used for listing
// and session picker display.
type SessionInfo = session.SessionInfo

// TreeManager manages a tree-structured JSONL session with branching,
// leaf-pointer tracking, and context building.
type TreeManager = session.TreeManager

// SessionHeader is the first line in a JSONL session file, carrying metadata.
type SessionHeader = session.SessionHeader

// MessageEntry stores a conversation message as a tree entry in JSONL sessions.
type MessageEntry = session.MessageEntry

// ==== Config Types (internal/config/) ====

// Config represents the complete application configuration including MCP
// servers, model settings, UI preferences, and API credentials.
type Config = config.Config

// MCPServerConfig represents configuration for an MCP server, supporting both
// local (stdio) and remote (StreamableHTTP/SSE) server types.
type MCPServerConfig = config.MCPServerConfig

// ==== Agent Types ====

// DebugLogger is an SDK-owned interface for low-level debug logging from
// the engine and MCP tool plumbing. Implementations must be safe for
// concurrent use.
//
// Most consumers do not need to provide one; pass [Options.Debug] = true
// to use the default logger. DebugLogger is exposed for the low-level
// [AgentConfig] path and for embedders that want to route debug output
// into their own logging system.
type DebugLogger interface {
	// LogDebug records a single debug message. Implementations may drop,
	// buffer, or render the message however they choose.
	LogDebug(message string)
	// IsDebugEnabled reports whether debug logging is active. Callers may
	// check this before doing expensive formatting work.
	IsDebugEnabled() bool
}

// AgentConfig holds configuration options for constructing an agent at the
// SDK boundary. All fields use SDK-owned types, so consumers can populate
// this struct without importing any underlying LLM-provider package.
//
// For most use cases, prefer the high-level [New] entry point with
// [Options]. AgentConfig is exposed for advanced consumers that need
// direct access to the lower-level agent configuration shape.
type AgentConfig struct {
	// ModelConfig holds the LLM provider configuration. A nil value means
	// that the default provider/model resolution will be used.
	ModelConfig *ProviderConfig

	// MCPConfig describes any MCP servers whose tools should be loaded
	// alongside core tools.
	MCPConfig *Config

	// SystemPrompt is the system prompt sent to the LLM.
	SystemPrompt string

	// MaxSteps caps the number of LLM iterations per turn. A value of
	// zero means no cap is applied at this layer.
	MaxSteps int

	// StreamingEnabled controls whether the agent streams responses.
	StreamingEnabled bool

	// AuthHandler handles OAuth authorization for remote MCP servers.
	// When nil, remote MCP servers requiring OAuth will fail to connect.
	AuthHandler MCPAuthHandler

	// TokenStoreFactory, if non-nil, creates a custom token store for each
	// remote MCP server's OAuth tokens. When nil, the default file-based
	// token store is used.
	TokenStoreFactory MCPTokenStoreFactory

	// CoreTools overrides the default core tool set. If empty, [AllTools]
	// is used. Provide a custom tool set (e.g. [CodingTools] or tools
	// built with a custom WorkDir) to scope agent capabilities.
	CoreTools []Tool

	// DisableCoreTools, when true, prevents loading any core tools.
	// Combined with empty CoreTools this yields a chat-only agent with
	// no built-in tools.
	DisableCoreTools bool

	// ExtraTools are additional tools loaded alongside core and MCP tools.
	ExtraTools []Tool

	// ToolWrapper, if non-nil, wraps the combined tool list before it is
	// handed to the LLM. Used to intercept tool calls or results.
	ToolWrapper func([]Tool) []Tool

	// OnMCPServerLoaded, if non-nil, is invoked once for each MCP server
	// when its tools have finished loading (or failed). Called from a
	// background goroutine.
	OnMCPServerLoaded func(serverName string, toolCount int, err error)

	// DebugLogger receives low-level debug output from the engine and the
	// MCP tool plumbing. Nil means no debug output is emitted at this
	// layer (regardless of [Options.Debug], which feeds the higher-level
	// [New] entry point). Pass an implementation here when wiring a custom
	// logger through the lower-level AgentConfig path.
	DebugLogger DebugLogger

	// MCPTaskConfig configures task-aware MCP tools/call execution — mode
	// overrides, polling intervals, timeouts, and the progress handler.
	// The zero value preserves historical synchronous-only behaviour for
	// any server that didn't advertise task support during initialize.
	MCPTaskConfig MCPTaskConfig
}

// toInternal converts an AgentConfig to its internal representation.
// Slice and function fields convert without allocation because [Tool]
// is a type alias for the underlying LLM-tool type.
func (c *AgentConfig) toInternal() *agent.AgentConfig {
	if c == nil {
		return nil
	}
	out := &agent.AgentConfig{
		ModelConfig:       c.ModelConfig,
		MCPConfig:         c.MCPConfig,
		SystemPrompt:      c.SystemPrompt,
		MaxSteps:          c.MaxSteps,
		StreamingEnabled:  c.StreamingEnabled,
		CoreTools:         c.CoreTools,
		DisableCoreTools:  c.DisableCoreTools,
		ExtraTools:        c.ExtraTools,
		ToolWrapper:       c.ToolWrapper,
		OnMCPServerLoaded: c.OnMCPServerLoaded,
	}
	if c.AuthHandler != nil {
		out.AuthHandler = c.AuthHandler
	}
	if c.TokenStoreFactory != nil {
		out.TokenStoreFactory = tools.TokenStoreFactory(c.TokenStoreFactory)
	}
	if c.DebugLogger != nil {
		out.DebugLogger = c.DebugLogger
	}
	out.MCPTaskConfig = c.MCPTaskConfig.toToolsConfig()
	return out
}

// ToolCallHandler is invoked when the LLM produces a tool call. It receives
// the call ID, tool name, and the JSON-encoded input arguments.
type ToolCallHandler func(toolCallID, toolName, toolArgs string)

// ToolExecutionHandler is invoked at the start and end of tool execution.
// The isStarting flag distinguishes the two phases.
type ToolExecutionHandler func(toolCallID, toolName, toolArgs string, isStarting bool)

// ToolResultHandler is invoked after a tool finishes executing. The metadata
// parameter carries optional structured data (e.g. file-diff info) from the
// tool execution, JSON-encoded; it may be empty.
type ToolResultHandler func(toolCallID, toolName, toolArgs, result, metadata string, isError bool)

// ResponseHandler is invoked with the final assistant text for each turn.
type ResponseHandler func(content string)

// StreamingResponseHandler is invoked with each streamed text delta as it
// arrives from the LLM.
type StreamingResponseHandler func(content string)

// ToolCallContentHandler is invoked with any assistant text that accompanies
// a tool call within the same step.
type ToolCallContentHandler func(content string)

// ==== Provider & Model Types (internal/models/) ====

// ProviderConfig holds configuration for creating LLM providers.
type ProviderConfig = models.ProviderConfig

// ProviderResult contains the result of provider creation (model + optional
// feedback message + closer).
type ProviderResult = models.ProviderResult

// ModelInfo represents information about a specific model (capabilities,
// pricing, limits).
type ModelInfo = models.ModelInfo

// ModelCost represents the pricing information for a model.
type ModelCost = models.Cost

// ModelLimit represents the context and output limits for a model.
type ModelLimit = models.Limit

// ProviderInfo represents information about a model provider from the
// models.dev database.
type ProviderInfo = models.ProviderInfo

// ModelsRegistry provides validation and information about models, maintaining
// a registry of all supported LLM providers and their models.
type ModelsRegistry = models.ModelsRegistry

// ==== Agent Callback Types ====

// SpinnerFunc wraps a function in a loading spinner animation. Used for
// Ollama model loading. Signature: func(fn func() error) error.
type SpinnerFunc func(fn func() error) error

// ==== LLM Types ====
//
// These are type aliases for the underlying LLM provider types, giving them
// clean LLM-prefixed names without leaking the dependency name. SDK consumers
// can use these types without importing the provider package directly.

// LLMMessage represents a message in an LLM conversation, carrying a role
// and a slice of typed content parts (text, tool calls, reasoning, etc.).
type LLMMessage = fantasy.Message

// LLMMessagePart is the interface implemented by all LLM message content parts.
type LLMMessagePart = fantasy.MessagePart

// LLMFilePart represents a file attachment (image, document, audio, etc.)
// that can be included in a multimodal prompt via PromptResultWithFiles.
type LLMFilePart = fantasy.FilePart

// LLMUsage contains token usage information returned by the LLM provider.
type LLMUsage = fantasy.Usage

// LLMResponse represents a complete response from the LLM provider.
type LLMResponse = fantasy.Response

// LLMTextPart is a plain-text content part for constructing LLM messages.
type LLMTextPart = fantasy.TextPart

// LLMReasoningPart is a reasoning/chain-of-thought content part.
type LLMReasoningPart = fantasy.ReasoningPart

// LLMToolCall represents the raw tool invocation passed to a [Tool]'s Run
// method. It carries the call ID, tool name, and the JSON-encoded input
// arguments from the LLM. This is the execution-layer call object — distinct
// from [ToolCall] (a message content part).
type LLMToolCall = fantasy.ToolCall

// LLMToolResponse represents the raw response returned from a [Tool]'s Run
// method. Most SDK consumers should use [ToolOutput] with [NewTool] /
// [NewParallelTool] instead — this alias is provided for advanced use cases
// that need to call Tool.Run() directly (e.g. testing).
type LLMToolResponse = fantasy.ToolResponse

// LLMToolCallPart represents an LLM-initiated tool invocation within a message.
type LLMToolCallPart = fantasy.ToolCallPart

// LLMToolResultPart represents the result of a tool execution within a message.
type LLMToolResultPart = fantasy.ToolResultPart

// LLMToolResultOutputContent is the interface for tool result output content.
type LLMToolResultOutputContent = fantasy.ToolResultOutputContent

// LLMToolResultOutputContentText is a text-valued tool result output.
type LLMToolResultOutputContentText = fantasy.ToolResultOutputContentText

// LLMToolResultOutputContentError is an error-valued tool result output.
type LLMToolResultOutputContentError = fantasy.ToolResultOutputContentError

// LLMToolResultOutputContentMedia is a media-valued tool result output
// (images, audio, etc.) carrying base64-encoded data and a MIME type.
type LLMToolResultOutputContentMedia = fantasy.ToolResultOutputContentMedia

// LLMToolResultContentType classifies the kind of a tool result output
// ("text", "error", or "media").
type LLMToolResultContentType = fantasy.ToolResultContentType

// Tool result content type constants.
const (
	// LLMToolResultContentTypeText represents text output.
	LLMToolResultContentTypeText = fantasy.ToolResultContentTypeText
	// LLMToolResultContentTypeError represents error text output.
	LLMToolResultContentTypeError = fantasy.ToolResultContentTypeError
	// LLMToolResultContentTypeMedia represents media (binary) output.
	LLMToolResultContentTypeMedia = fantasy.ToolResultContentTypeMedia
)

// LLMToolInfo describes a tool's name, description, and JSON-Schema parameters.
type LLMToolInfo = fantasy.ToolInfo

// LLMProviderOptions carries provider-specific key/value option maps, keyed
// by provider name (e.g. "anthropic"). Use this when configuring or
// inspecting provider-specific tool behaviour.
type LLMProviderOptions = fantasy.ProviderOptions

// LLMProviderMetadata carries provider-specific metadata returned alongside
// LLM responses, keyed by provider name.
type LLMProviderMetadata = fantasy.ProviderMetadata

// LLMPrompt is an ordered sequence of [LLMMessage] values forming a complete
// prompt for the LLM.
type LLMPrompt = fantasy.Prompt

// LLMMessageRole identifies the participant role in an LLM conversation.
type LLMMessageRole = fantasy.MessageRole

// LLMFinishReason indicates why the LLM stopped generating.
type LLMFinishReason = fantasy.FinishReason

// LLM role constants mirror the provider's role values under clean LLM-prefixed names.
const (
	// LLMRoleUser identifies a user message.
	LLMRoleUser = fantasy.MessageRoleUser
	// LLMRoleAssistant identifies an assistant message.
	LLMRoleAssistant = fantasy.MessageRoleAssistant
	// LLMRoleSystem identifies a system message.
	LLMRoleSystem = fantasy.MessageRoleSystem
	// LLMRoleTool identifies a tool result message.
	LLMRoleTool = fantasy.MessageRoleTool
)

// NewLLMUserMessage constructs a user-role LLMMessage with optional file
// attachments.
var NewLLMUserMessage = fantasy.NewUserMessage

// NewLLMSystemMessage constructs a system-role LLMMessage from one or more
// prompt strings.
var NewLLMSystemMessage = fantasy.NewSystemMessage

// newLLMTextErrorResponse creates a tool-error response (internal helper).
var newLLMTextErrorResponse = fantasy.NewTextErrorResponse

// newLLMTextResponse creates a plain-text tool response (internal helper).
var newLLMTextResponse = fantasy.NewTextResponse

// ==== Compaction Types (internal/compaction/) ====

// CompactionResult contains statistics from a compaction operation.
type CompactionResult = compaction.CompactionResult

// CompactionOptions configures compaction behaviour.
type CompactionOptions = compaction.CompactionOptions

// ==== MCP OAuth Types ====

// MCPServer is an in-process MCP server from the mcp-go library.
// Pass an instance to [Kit.AddInProcessMCPServer] or
// [Options.InProcessMCPServers] to register tools without spawning a
// subprocess or making network calls.
type MCPServer = server.MCPServer

// MCPTokenStore persists OAuth tokens for a single MCP server. Implementations
// must be safe for concurrent use.
//
// This is a type alias for the mcp-go transport.TokenStore interface. SDK
// consumers can implement this interface to provide custom storage backends
// (database, encrypted file, in-memory, etc.).
type MCPTokenStore = transport.TokenStore

// MCPToken represents an OAuth token for an MCP server, containing access
// and refresh tokens along with expiration metadata.
type MCPToken = transport.Token

// MCPTokenStoreFactory creates an [MCPTokenStore] for a given MCP server URL.
// It is called once per remote MCP server during connection setup.
type MCPTokenStoreFactory func(serverURL string) (MCPTokenStore, error)

// ErrMCPNoToken is the sentinel error that [MCPTokenStore] implementations
// should return from GetToken when no token is stored for the server.
// Callers can check for this with errors.Is.
var ErrMCPNoToken = transport.ErrNoToken

// ==== Constructor & Helper Functions ====

// ParseModelString parses a model string in "provider/model" format.
// Returns provider, modelID, and an error if the format is invalid.
func ParseModelString(model string) (provider, modelID string, err error) {
	return models.ParseModelString(model)
}

// CreateProvider creates a LanguageModel based on provider config.
func CreateProvider(ctx context.Context, cfg *ProviderConfig) (*ProviderResult, error) {
	return models.CreateProvider(ctx, cfg)
}

// GetGlobalRegistry returns the global models registry instance.
func GetGlobalRegistry() *ModelsRegistry {
	return models.GetGlobalRegistry()
}

// LoadSystemPrompt loads a system prompt from a file path, or returns the
// string directly if it is not a valid file path.
func LoadSystemPrompt(pathOrContent string) (string, error) {
	return config.LoadSystemPrompt(pathOrContent)
}

// ==== Conversion Helpers ====

// ConvertToLLMMessages converts an SDK message to a slice of LLMMessages.
// Each SDK message may expand to multiple LLM messages depending on its content.
func ConvertToLLMMessages(msg *Message) []LLMMessage {
	return msg.ToLLMMessages()
}

// ConvertFromLLMMessage converts an LLMMessage to an SDK message.
func ConvertFromLLMMessage(msg LLMMessage) Message {
	return message.FromLLMMessage(msg)
}
