// Package agentview owns the deterministic JSON projection of an Agent.
package agentview

import (
	"strings"

	invopopjsonschema "github.com/invopop/jsonschema"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

// Snapshot is the canonical Agent wire projection shared by local APIs.
type Snapshot struct {
	ID                string   `json:"id"`
	Name              string   `json:"name,omitempty"`
	Runtime           string   `json:"runtime"`
	Description       string   `json:"description,omitempty"`
	Labels            []string `json:"labels,omitempty"`
	BaseURL           string   `json:"baseUrl,omitempty"`
	APIKeyConfigured  bool     `json:"apiKeyConfigured,omitempty"`
	Headers           Headers  `json:"headers,omitempty"`
	WireAPI           string   `json:"wireApi,omitempty"`
	Model             string   `json:"model,omitempty"`
	SmallModel        string   `json:"smallModel,omitempty"`
	ReasoningEffort   string   `json:"reasoningEffort,omitempty"`
	Policy            *Policy  `json:"policy,omitempty"`
	RuntimeType       string   `json:"runtimeType,omitempty"`
	Status            string   `json:"status"`
	Endpoint          string   `json:"endpoint,omitempty"`
	BackendRegistered bool     `json:"backendRegistered"`
	Source            string   `json:"source,omitempty"`
	SourceType        string   `json:"sourceType,omitempty"`
	SourceName        string   `json:"sourceName,omitempty"`
}

// Headers is the bounded string dictionary used for Agent upstream headers.
type Headers map[string]string

// JSONSchema describes Headers as an object with string values.
func (Headers) JSONSchema() *invopopjsonschema.Schema {
	return &invopopjsonschema.Schema{
		Type:                 "object",
		AdditionalProperties: &invopopjsonschema.Schema{Type: "string"},
	}
}

// Policy is the canonical Agent launch-limits projection.
type Policy struct {
	MaxConcurrentSessions int      `json:"maxConcurrentTasks,omitempty"`
	Timeout               string   `json:"timeout,omitempty"`
	AllowedPaths          []string `json:"allowedPaths,omitempty"`
}

// BuildContext contains composition-owned state used to build a Snapshot.
type BuildContext struct {
	SourceName  string
	RuntimeType string
	Session     *types.SessionSnapshot
}

// Build creates a pure, detached Snapshot from one Agent configuration.
func Build(configured agent.Config, context BuildContext) Snapshot {
	backendRegistered := strings.TrimSpace(configured.Source.Type) == "agentteam"
	result := Snapshot{
		ID:                configured.ID,
		Name:              configured.Name,
		Runtime:           configured.Runtime,
		Description:       configured.Description,
		Labels:            cloneStrings(configured.Labels),
		Model:             configured.Model,
		SmallModel:        configured.SmallModel,
		ReasoningEffort:   configured.ReasoningEffort,
		RuntimeType:       context.RuntimeType,
		Status:            registrationStatus(backendRegistered),
		BackendRegistered: backendRegistered,
		Source:            configured.Source.String(),
		SourceType:        configured.Source.Type,
		SourceName:        context.SourceName,
	}

	_, baseURL, apiKey, headers, wireAPI := agent.NormalizeAgentLLMFields(
		configured.LLMMode,
		configured.BaseURL,
		configured.APIKey,
		configured.Headers,
		configured.WireAPI,
	)
	if hasExplicitLLMOverrides(baseURL, apiKey, headers, wireAPI) {
		result.BaseURL = baseURL
		result.APIKeyConfigured = strings.TrimSpace(apiKey) != ""
		result.Headers = cloneHeaders(headers)
		result.WireAPI = wireAPI
	}

	if configured.Policy.MaxConcurrentSessions != 0 ||
		configured.Policy.Timeout != "" ||
		len(configured.Policy.AllowedPaths) > 0 {
		result.Policy = &Policy{
			MaxConcurrentSessions: configured.Policy.MaxConcurrentSessions,
			Timeout:               configured.Policy.Timeout,
			AllowedPaths:          cloneStrings(configured.Policy.AllowedPaths),
		}
	}

	if context.Session != nil {
		if strings.TrimSpace(context.Session.ServerAgentID) != "" || backendRegistered {
			result.BackendRegistered = true
		}
		status := strings.TrimSpace(context.Session.Status)
		switch strings.ToLower(status) {
		case "stopped", "inactive":
		case "":
			result.Status = "active"
		default:
			result.Status = status
		}
	}
	return result
}

func cloneStrings(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	return append([]string(nil), values...)
}

func cloneHeaders(values map[string]string) Headers {
	if len(values) == 0 {
		return nil
	}
	result := make(Headers, len(values))
	for key, value := range values {
		result[key] = value
	}
	return result
}

func registrationStatus(backendRegistered bool) string {
	if backendRegistered {
		return "registered"
	}
	return "pending-registration"
}

func hasExplicitLLMOverrides(baseURL, apiKey string, headers map[string]string, wireAPI string) bool {
	return strings.TrimSpace(baseURL) != "" ||
		strings.TrimSpace(apiKey) != "" ||
		strings.TrimSpace(wireAPI) != "" ||
		len(headers) > 0
}
