/**
 * Shared types for the LLM inference proxy.
 *
 * The wire format on the mcpctl side is OpenAI's chat/completions v1 — it's
 * the de-facto lingua franca and every client library already speaks it.
 * Provider-specific adapters translate to/from that shape.
 */

export interface OpenAiMessage {
  role: 'system' | 'user' | 'assistant' | 'tool';
  content: string | Array<{ type: string; text?: string; [k: string]: unknown }>;
  name?: string;
  tool_call_id?: string;
  tool_calls?: Array<{ id: string; type: 'function'; function: { name: string; arguments: string } }>;
}

export interface OpenAiChatRequest {
  model: string;
  messages: OpenAiMessage[];
  stream?: boolean;
  temperature?: number;
  max_tokens?: number;
  top_p?: number;
  stop?: string | string[];
  tools?: Array<{ type: 'function'; function: { name: string; description?: string; parameters?: Record<string, unknown> } }>;
  tool_choice?: unknown;
  // Passthrough: unknown extras forwarded as-is.
  [k: string]: unknown;
}

export interface InferContext {
  /** Normalised OpenAI-format body. Adapters read/transform from here. */
  body: OpenAiChatRequest;
  /** The Llm row's `model` field, used when the request body has an empty model. */
  modelOverride: string;
  /** The resolved API key, or empty string for providers that don't take one. */
  apiKey: string;
  /** Target URL from the Llm row (may be empty for provider-default). */
  url: string;
  /** Arbitrary config from the Llm row (e.g. vllm gpu settings). */
  extraConfig: Record<string, unknown>;
}

export interface NonStreamingResult {
  status: number;
  /** OpenAI chat.completion response body. */
  body: unknown;
}

export interface StreamingChunk {
  /** Raw SSE data payload. Consumer emits `data: <payload>\n\n`. */
  data: string;
  /** Mark the end of stream — consumer emits `data: [DONE]\n\n`. */
  done?: boolean;
}

export interface LlmAdapter {
  readonly kind: string;
  /** Non-streaming request. Returns the final chat.completion body. */
  infer(ctx: InferContext): Promise<NonStreamingResult>;
  /**
   * Streaming request. Yields OpenAI-format SSE chunks. Adapters translate
   * provider-native stream formats into OpenAI `chat.completion.chunk`s.
   */
  stream(ctx: InferContext): AsyncGenerator<StreamingChunk>;
  /**
   * Cheap auth probe used at Llm create/update time. Should pick the cheapest
   * upstream call that exercises the auth header — typically a list-models
   * endpoint or a 1-token messages call.
   *
   * Returns one of:
   *   - { ok: true }                                  — auth succeeded
   *   - { ok: false, reason: 'auth', status, body }  — upstream said no (401/403)
   *   - { ok: false, reason: 'unreachable', error }  — network/DNS/timeout
   *   - { ok: false, reason: 'unexpected', status, body } — couldn't tell
   *
   * Callers (LlmService.create/update) throw on `auth`, warn-only on
   * `unreachable`, and warn-only on `unexpected`. The point is to fail fast on
   * provably wrong credentials at registration time.
   */
  verifyAuth(ctx: InferContext): Promise<VerifyAuthResult>;
}

export type VerifyAuthResult =
  | { ok: true }
  | { ok: false; reason: 'auth'; status: number; body: string }
  | { ok: false; reason: 'unreachable'; error: string }
  | { ok: false; reason: 'unexpected'; status: number; body: string };

export interface AdapterDeps {
  fetch?: typeof globalThis.fetch;
}
