/**
 * Provider-agnostic LLM streaming abstraction.
 *
 * Each provider implements `generate(opts)` returning an
 * AsyncIterable that yields text deltas as the model produces them,
 * then a final usage chunk + done. This normalizes the wildly
 * different streaming formats (Anthropic SSE / OpenAI SSE / Gemini
 * streaming JSON / Ollama NDJSON) behind one shape.
 *
 * The system prompt + user prompt + a strict-JSON instruction is
 * what every provider receives. We DON'T use provider-specific
 * "JSON mode" / "structured output" features because they aren't
 * uniformly available (notably Ollama varies by model). Instead the
 * prompt itself instructs the model to emit ONLY valid JSON; the
 * server validates with Zod after.
 */

export type ProviderId =
  | 'claude'
  | 'openai'
  | 'openai-compatible'
  | 'gemini'
  | 'ollama';

export interface GenerateOpts {
  /** API key. Null/undefined for ollama on a trusted LAN. */
  apiKey?: string | null;
  /**
   * Provider base URL.
   *  - openai-compatible: required (e.g. https://openrouter.ai/api/v1)
   *  - ollama:           required (e.g. http://ollama.embassy:11434)
   *  - others:           ignored, provider hits its hardcoded endpoint
   */
  baseUrl?: string | null;
  /** Model identifier as the provider expects it. */
  model: string;
  systemPrompt: string;
  userPrompt: string;
  /** AbortSignal for cancellation; the implementation must respect it. */
  signal?: AbortSignal;
  /**
   * v1.1.0:4: explicit max output token budget. Providers honor this
   * differently — used to make small "test connection" calls survive
   * thinking models (Gemini 2.5+, OpenAI o-series) that may spend
   * their default budget on internal reasoning before emitting visible
   * text. Default per-provider when omitted.
   */
  maxOutputTokens?: number;
}

export type GenerateChunk =
  | { type: 'text'; delta: string }
  | { type: 'usage'; tokensIn?: number; tokensOut?: number }
  | { type: 'done' }
  | { type: 'error'; message: string };

export interface LLMProvider {
  id: ProviderId;
  /** Display label for the Settings UI dropdown. */
  label: string;
  /** Whether the provider needs an API key. */
  requiresApiKey: boolean;
  /** Whether the provider needs a custom base URL. */
  requiresBaseUrl: boolean;
  /** Async stream of chunks. */
  generate(opts: GenerateOpts): AsyncIterable<GenerateChunk>;
}
