'use strict';

const { toOpenAI, messagesToOpenAI, responseFromOpenAI } = require('./tool-adapter');

function isGemma4Model(model) {
  return /^gemma4:/i.test(String(model || ''));
}

function isReasoningCapableOllamaModel(model) {
  return /^(gemma4:|gpt-oss\b|deepseek-r1\b|qwen3\b|qwq\b|phi-?4.*reason)/i.test(String(model || ''));
}

function capabilitiesForOllamaModel(model) {
  if (/embed|embedding/i.test(String(model || ''))) return ['embedding'];
  const caps = ['chat', 'code', 'tools'];
  if (isReasoningCapableOllamaModel(model)) caps.push('reasoning');
  return caps;
}

function resolveThinkValue({ model, thinking, reasoningEffort } = {}) {
  if (thinking === false || thinking === 'disabled' || thinking === 'off') return false;
  if (thinking === true || thinking === 'enabled' || thinking === 'on') return true;
  if (['low', 'medium', 'high'].includes(thinking)) return thinking;

  // Ollama's GPT-OSS models accept effort levels. Gemma4's thinking renderer
  // accepts the boolean form, so keep Gemma4 on true when callers ask for any
  // non-disabled reasoning effort.
  if (reasoningEffort && reasoningEffort !== 'disabled') {
    if (/^gpt-oss/i.test(String(model || ''))) {
      if (reasoningEffort === 'max') return 'high';
      if (['low', 'medium', 'high'].includes(reasoningEffort)) return reasoningEffort;
    }
    return true;
  }
  return undefined;
}

function buildOllamaOptions({ model, maxTokens, temperature, options } = {}) {
  const out = { ...(options || {}) };
  if (maxTokens != null) out.num_predict = maxTokens;
  if (temperature != null) out.temperature = temperature;
  if (isGemma4Model(model) && out.num_ctx == null) {
    out.num_ctx = 16384;
  }
  return Object.keys(out).length ? out : undefined;
}

/**
 * Create an Ollama local-model LLM provider.
 * Uses Ollama's OpenAI-compatible API — no npm dependency needed.
 * @param {{ baseUrl?: string }} config
 */
function createOllamaProvider(config = {}) {
  const baseUrl = (config.baseUrl || 'http://localhost:11434').replace(/\/+$/, '');
  const defaultThink = config.think ?? false;

  return {
    type: 'ollama',

    async chat({ model, messages, tools, system, maxTokens, temperature, signal, thinking, reasoningEffort, options } = {}) {
      const modelId = model || 'gemma4:e4b';
      const openaiMessages = [];

      // System prompt as first message
      if (system) {
        openaiMessages.push({ role: 'system', content: system });
      }

      // Convert messages to OpenAI format
      if (messages && messages.length > 0) {
        openaiMessages.push(...messagesToOpenAI(messages));
      }

      // Use native Ollama API when no tools — it returns eval_duration for accurate gen speed
      const useNativeApi = !tools || tools.length === 0;

      if (useNativeApi) {
        const resolvedThink = resolveThinkValue({ model: modelId, thinking, reasoningEffort });
        const nativeBody = {
          model: modelId,
          messages: openaiMessages,
          stream: false,
          think: resolvedThink !== undefined ? resolvedThink : defaultThink,
        };
        const nativeOptions = buildOllamaOptions({ model: modelId, maxTokens, temperature, options });
        if (nativeOptions) nativeBody.options = nativeOptions;

        const start = Date.now();
        const resp = await fetch(`${baseUrl}/api/chat`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(nativeBody),
          signal,
        });

        if (!resp.ok) {
          const text = await resp.text().catch(() => '');
          throw new Error(`Ollama API error ${resp.status}: ${text}`);
        }

        const raw = await resp.json();
        const latencyMs = Date.now() - start;
        const msg = raw.message || {};
        const reasoningContent = msg.thinking || msg.reasoning || msg.reasoning_content || null;

        // Extract accurate timing from native API (durations are in nanoseconds)
        const evalDurationNs = raw.eval_duration || 0;
        const evalCount = raw.eval_count || 0;
        const promptEvalDurationNs = raw.prompt_eval_duration || 0;
        const promptEvalCount = raw.prompt_eval_count || 0;
        const genTokPerSec = evalDurationNs > 0 ? evalCount / (evalDurationNs / 1e9) : null;
        const prefillTokPerSec = promptEvalDurationNs > 0 ? promptEvalCount / (promptEvalDurationNs / 1e9) : null;

        return {
          content: msg.content || null,
          reasoningContent,
          toolCalls: [],
          stopReason: 'end_turn',
          usage: {
            input: promptEvalCount,
            output: evalCount,
            genTokPerSec,
            prefillTokPerSec,
            evalDurationMs: evalDurationNs / 1e6,
            promptEvalDurationMs: promptEvalDurationNs / 1e6,
          },
          latencyMs,
          model: modelId,
          provider: 'ollama',
          raw,
        };
      }

      // Fall back to OpenAI-compat API for tool use
      const resolvedThink = resolveThinkValue({ model: modelId, thinking, reasoningEffort });
      const body = {
        model: modelId,
        messages: openaiMessages,
        stream: false,
        think: resolvedThink !== undefined ? resolvedThink : defaultThink,
      };

      const openaiTools = toOpenAI(tools);
      if (openaiTools) body.tools = openaiTools;

      if (maxTokens != null) body.max_tokens = maxTokens;
      if (temperature != null) body.temperature = temperature;

      const start = Date.now();
      let resp = await fetch(`${baseUrl}/v1/chat/completions`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
        signal,
      });

      // Retry without tools if model doesn't support them
      if (!resp.ok && resp.status === 400 && body.tools) {
        const errText = await resp.text().catch(() => '');
        if (errText.includes('does not support tools')) {
          console.log(`[ollama] ${model} does not support tools — retrying without`);
          delete body.tools;
          resp = await fetch(`${baseUrl}/v1/chat/completions`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body),
            signal,
          });
        } else {
          throw new Error(`Ollama API error ${resp.status}: ${errText}`);
        }
      }

      if (!resp.ok) {
        const text = await resp.text().catch(() => '');
        throw new Error(`Ollama API error ${resp.status}: ${text}`);
      }

      const raw = await resp.json();
      const normalized = responseFromOpenAI(raw);

      let stopReason = 'end_turn';
      if (normalized.stopReason === 'tool_calls') stopReason = 'tool_use';
      else if (normalized.stopReason === 'stop' || !normalized.stopReason) stopReason = 'end_turn';

      return { ...normalized, stopReason, latencyMs: Date.now() - start, model: modelId, provider: 'ollama', raw };
    },

    async listModels() {
      try {
        const resp = await fetch(`${baseUrl}/api/tags`, {
          signal: AbortSignal.timeout(3000),
        });
        if (!resp.ok) return [];
        const data = await resp.json();
        return (data.models || [])
          // Drop embedding models — they're not chat-capable.
          .filter((m) => !/embed|embedding/i.test(m.name || ''))
          .map((m) => ({
            id: m.name,
            name: m.name,
            capabilities: capabilitiesForOllamaModel(m.name),
            size: m.size,
          }));
      } catch {
        return [];
      }
    },

    async isAvailable() {
      try {
        const resp = await fetch(`${baseUrl}/api/tags`, {
          signal: AbortSignal.timeout(3000),
        });
        return resp.ok;
      } catch {
        return false;
      }
    },
  };
}

module.exports = {
  createOllamaProvider,
  buildOllamaOptions,
  resolveThinkValue,
  isReasoningCapableOllamaModel,
  capabilitiesForOllamaModel,
};
