import { OLLAMA_API_URL } from '@/config/setting';

export type OllamaRole = 'system' | 'user' | 'assistant' | 'tool';

export interface OllamaChatMessage {
  role: OllamaRole;
  content: string;
}

export interface OllamaModelTag {
  name: string;
  modified_at?: string;
  size?: number;
  digest?: string;
  details?: any;
}

export interface OllamaTagsResponse {
  models: OllamaModelTag[];
}

export interface OllamaChatRequest {
  model: string;
  messages: OllamaChatMessage[];
  stream?: boolean;
  options?: {
    temperature?: number;
    top_p?: number;
    num_predict?: number;
  };
}

export interface OllamaChatResponseChunk {
  model?: string;
  created_at?: string;
  message?: { role: OllamaRole; content: string };
  done?: boolean;
  error?: string;
}

function normalizeBaseURL(baseURL: string) {
  return baseURL.replace(/\/+$/, '');
}

export async function listOllamaModels(opts?: { baseURL?: string }) {
  const baseURL = normalizeBaseURL(opts?.baseURL ?? OLLAMA_API_URL);
  const res = await fetch(`${baseURL}/api/tags`, { method: 'GET' });
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(
      `listOllamaModels failed: ${res.status} ${res.statusText}${
        text ? ` - ${text}` : ''
      }`
    );
  }
  return (await res.json()) as OllamaTagsResponse;
}

export async function ollamaChat(
  body: OllamaChatRequest,
  opts?: { baseURL?: string; signal?: AbortSignal }
) {
  const baseURL = normalizeBaseURL(opts?.baseURL ?? OLLAMA_API_URL);
  const res = await fetch(`${baseURL}/api/chat`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...body, stream: false }),
    signal: opts?.signal
  });
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(
      `ollamaChat failed: ${res.status} ${res.statusText}${
        text ? ` - ${text}` : ''
      }`
    );
  }
  return (await res.json()) as OllamaChatResponseChunk;
}

/**
 * Ollama native streaming is newline-delimited JSON objects, not SSE.
 */
export async function ollamaChatStream(
  body: Omit<OllamaChatRequest, 'stream'>,
  opts: {
    baseURL?: string;
    signal?: AbortSignal;
    onDelta: (text: string) => void;
    onDone?: () => void;
  }
) {
  const baseURL = normalizeBaseURL(opts.baseURL ?? OLLAMA_API_URL);
  const res = await fetch(`${baseURL}/api/chat`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...body, stream: true }),
    signal: opts.signal
  });

  if (!res.ok || !res.body) {
    const text = await res.text().catch(() => '');
    throw new Error(
      `ollamaChatStream failed: ${res.status} ${res.statusText}${
        text ? ` - ${text}` : ''
      }`
    );
  }

  const reader = res.body.getReader();
  const decoder = new TextDecoder('utf-8');
  let buffer = '';

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    let idx = buffer.indexOf('\n');
    while (idx !== -1) {
      const line = buffer.slice(0, idx).trim();
      buffer = buffer.slice(idx + 1);
      idx = buffer.indexOf('\n');

      if (!line) continue;

      let json: OllamaChatResponseChunk;
      try {
        json = JSON.parse(line);
      } catch {
        continue;
      }

      if (json.error) {
        throw new Error(json.error);
      }

      const delta = json.message?.content;
      if (delta) {
        opts.onDelta(delta);
      }

      if (json.done) {
        opts.onDone?.();
        return;
      }
    }
  }

  opts.onDone?.();
}
