// This file defines the unified Provider interface and its dual failure model
// (US-007). A Provider turns a CompletionRequest into a stream of
// AssistantMessageEvents. Failures follow the same contract as StreamFn (FR-13):
//
//   - "cannot even build the stream" (bad config, missing model) → returned error.
//   - any runtime failure once streaming has begun → a terminal StreamErrorEvent
//     carrying an assistant message with stopReason=error/aborted, after which
//     the stream is closed. It is never a returned error.
//
// Model carries provider-agnostic capability metadata so the loop and UI can
// reason about a model without knowing the concrete provider.
package provider

import (
	"context"

	"github.com/smallnest/pigo/internal/agentcore"
)

// Model is provider-agnostic metadata describing a single model's identity and
// capabilities. Providers construct these; the loop/UI consume them.
type Model struct {
	// Provider is the provider name (e.g. "anthropic", "openai").
	Provider string `json:"provider"`
	// ID is the provider-specific model id (e.g. "claude-opus-4-8").
	ID string `json:"id"`
	// DisplayName is a human-friendly label; falls back to ID when empty.
	DisplayName string `json:"displayName,omitempty"`
	// ContextWindow is the maximum input+output token window, 0 if unknown.
	ContextWindow int `json:"contextWindow,omitempty"`
	// MaxOutputTokens is the max tokens the model may emit per response, 0 if
	// unknown.
	MaxOutputTokens int `json:"maxOutputTokens,omitempty"`
	// SupportsThinking reports whether the model exposes a reasoning/thinking
	// channel.
	SupportsThinking bool `json:"supportsThinking,omitempty"`
	// SupportsTools reports whether the model can call tools.
	SupportsTools bool `json:"supportsTools,omitempty"`
	// SupportsImages reports whether the model accepts image (multimodal) input.
	// When false, an image block in the request is reported as a hard error
	// rather than silently dropped, so the user learns the model cannot see it.
	SupportsImages bool `json:"supportsImages,omitempty"`
	// ThinkingLevels maps unified thinking levels to this model's wire values.
	// nil when the model does not support thinking (decision #10).
	ThinkingLevels agentcore.ThinkingLevelMap `json:"-"`
}

// CompletionRequest is the provider-agnostic input to StreamCompletion: the
// model id, the shaped LLM context, and per-request options.
type CompletionRequest struct {
	// Model is the provider-specific model id to complete against.
	Model string
	// Context is the shaped request (system prompt, LLM-bound messages, tools).
	Context LlmContext
	// Config carries per-request options (API key, thinking level, extras).
	Config StreamConfig
}

// Provider is the unified streaming interface implemented by every backend. It
// hides per-vendor differences behind a single AssistantMessageEvent stream.
type Provider interface {
	// Name returns the provider's identifier (matches Model.Provider).
	Name() string
	// Models lists the models this provider can serve.
	Models() []Model
	// StreamCompletion streams a completion for req. Per the dual failure model
	// it returns an error only for the earliest "cannot build the stream" case;
	// all runtime failures ride the returned stream as a terminal error event.
	StreamCompletion(ctx context.Context, req CompletionRequest) (*AssistantMessageEventStream, error)
}

// StreamFnFromProvider adapts a Provider to the loop's StreamFn contract so a
// Provider can drive streamAssistantResponse directly. The two failure models
// are identical, so the adaptation is a straight delegation.
func StreamFnFromProvider(p Provider) StreamFn {
	return func(ctx context.Context, model string, llm LlmContext, cfg StreamConfig) (*AssistantMessageEventStream, error) {
		return p.StreamCompletion(ctx, CompletionRequest{Model: model, Context: llm, Config: cfg})
	}
}
