// Package llm — провайдер LLM за интерфейсом (дискриминатор type).
//
// Реализация выбирается полем [llm].type (см. openspec/specs/recognition/spec.md).
// Первый и пока единственный тип — "openai-compat": OpenAI-совместимый Chat
// Completions API (локальные серверы LM Studio/llama.cpp/Ollama и облачные
// совместимые провайдеры — DeepSeek, Qwen и др.).
//
// Пакет отвечает за один вызов модели и транспортную устойчивость (ретраи
// на сетевые сбои, 429 и 5xx). Бюджет переразбора ответа со схемой-в-промпте
// (llm.max_retries) принадлежит вызывающему recognize, а не провайдеру.
package llm

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"time"
)

// Role — роль сообщения в диалоге.
type Role string

const (
	RoleSystem    Role = "system"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
)

// Message — одно сообщение запроса.
type Message struct {
	Role    Role
	Content string
}

// Request — запрос к модели.
type Request struct {
	Messages    []Message
	JSONMode    bool     // response_format: json_object (структурированный вывод)
	Temperature *float64 // nil — не передаём, у модели остаётся её дефолт
	MaxTokens   int      // 0 — не передаём
}

// Usage — расход токенов и стоимость (если провайдер их сообщает).
type Usage struct {
	PromptTokens     int
	CompletionTokens int
	TotalTokens      int
	Cost             float64
}

// Response — ответ модели.
type Response struct {
	Content string
	Model   string
	Usage   Usage
}

// Provider — абстракция доступа к LLM. recognize работает только с ним и не
// знает про конкретный транспорт.
type Provider interface {
	Complete(ctx context.Context, req Request) (Response, error)
}

// Config — параметры провайдера (подмножество [llm] из конфига).
type Config struct {
	Type    string
	BaseURL string
	APIKey  string
	Model   string
	Proxy   string
	Timeout time.Duration
}

// ErrUnknownType — запрошенный [llm].type не поддерживается.
var ErrUnknownType = errors.New("llm: unknown provider type")

// New собирает провайдер по дискриминатору cfg.Type. logger nil → slog.Default().
func New(cfg Config, logger *slog.Logger) (Provider, error) {
	if logger == nil {
		logger = slog.Default()
	}
	switch cfg.Type {
	case "openai-compat":
		return newOpenAICompat(cfg, logger)
	case "":
		return nil, fmt.Errorf("%w: %q (set [llm].type)", ErrUnknownType, cfg.Type)
	default:
		return nil, fmt.Errorf("%w: %q", ErrUnknownType, cfg.Type)
	}
}
