/**
 * One-shot model completion for the Arena analyzer.
 *
 * Resolves a model id (composite "provider/model" or bare) against the
 * provider registry, then calls the correct provider's baseUrl directly.
 * Used for the digest and judge stages (always DEFAULT_MODEL) and for
 * local-model cross-examinations (any local model).
 *
 * Mirrors apps/server/src/services/task-model.ts but targets the coder's
 * config shape and uses a longer timeout appropriate for analysis calls.
 */

import {
  parseModelRef as parseModelRefBase,
  getLlamaProviders,
} from './llama-providers.js';

const TIMEOUT_MS = 120_000;

/**
 * Resolve a model id to { baseUrl, wireModelId } against the provider registry.
 * Composite "provider/model" is parsed; bare ids resolve to the default provider.
 */
export function resolveModelEndpoint(
  model: string,
): { baseUrl: string; wireModelId: string } {
  const ref = parseModelRefBase(model);
  const providers = getLlamaProviders();
  const provider = providers.providers.find((p) => p.id === ref.providerId);
  if (!provider) {
    throw new Error(`unknown provider: ${ref.providerId} (model: ${model})`);
  }
  return { baseUrl: provider.baseUrl, wireModelId: ref.wireModelId };
}

export async function arenaModelCall(opts: {
  model: string;
  system: string;
  user: string;
  maxTokens?: number;
  temperature?: number;
}): Promise<string> {
  const { model, system, user } = opts;
  const maxTokens = opts.maxTokens ?? 2_000;
  const temperature = opts.temperature ?? 0.3;

  const { baseUrl, wireModelId } = resolveModelEndpoint(model);

  const res = await fetch(`${baseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-Boo-Source': 'arena' },
    body: JSON.stringify({
      model: wireModelId,
      messages: [
        { role: 'system', content: system },
        { role: 'user', content: user },
      ],
      max_tokens: maxTokens,
      temperature,
      stream: false,
      chat_template_kwargs: { enable_thinking: false },
    }),
    signal: AbortSignal.timeout(TIMEOUT_MS),
  });

  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`model endpoint responded ${res.status}: ${text.slice(0, 200)}`);
  }

  const data = (await res.json()) as {
    choices?: Array<{
      message?: { content?: string; reasoning_content?: string };
    }>;
  };

  const choice = data.choices?.[0]?.message;
  if (!choice) return '';

  const content = (choice.content ?? '').trim();
  if (content.length > 0) return content;

  // For thinking-mode models the answer sometimes only lands in reasoning_content.
  const reasoning = (choice.reasoning_content ?? '').trim();
  if (reasoning.length > 0) {
    const lines = reasoning.split('\n').filter((l) => l.trim().length > 0);
    return lines[lines.length - 1] ?? '';
  }

  return '';
}
