export interface ChatMessage {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

export interface AiProvider {
  name: string;
  chat(
    messages: ChatMessage[],
    model: string,
    onToken: (token: string) => void,
    onDone: (full: string, usage?: { tokensIn: number; tokensOut: number }) => void,
    onError: (err: Error) => void,
    signal?: AbortSignal,
  ): void;
}

export function createProvider(provider: string, apiKey: string, baseUrl?: string): AiProvider | null {
  switch (provider) {
    case 'openai': return openai(apiKey, baseUrl);
    case 'anthropic': return anthropic(apiKey);
    case 'ollama': return ollama(baseUrl);
    default: return null;
  }
}

// ── SSE line parser ──

async function readSSE(res: Response, onLine: (line: string) => void, signal?: AbortSignal) {
  const reader = res.body!.getReader();
  const dec = new TextDecoder();
  let buf = '';
  while (true) {
    if (signal?.aborted) break;
    const { done, value } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    const lines = buf.split('\n');
    buf = lines.pop()!;
    for (const line of lines) {
      if (line.startsWith('data: ') && line !== 'data: [DONE]') onLine(line.slice(6));
    }
  }
}

// ── Providers (raw fetch, zero deps) ──

function openai(apiKey: string, baseUrl = 'https://api.openai.com/v1'): AiProvider {
  return {
    name: 'openai',
    async chat(messages, model, onToken, onDone, onError, signal) {
      try {
        const res = await fetch(`${baseUrl}/chat/completions`, {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
          body: JSON.stringify({ model, messages, stream: true, stream_options: { include_usage: true } }),
          signal,
        });
        if (!res.ok) throw new Error(`OpenAI ${res.status}: ${await res.text()}`);
        let full = '';
        let usage: { tokensIn: number; tokensOut: number } | undefined;
        await readSSE(res, (line) => {
          const j = JSON.parse(line);
          const t = j.choices?.[0]?.delta?.content;
          if (t) { full += t; onToken(t); }
          if (j.usage) usage = { tokensIn: j.usage.prompt_tokens, tokensOut: j.usage.completion_tokens };
        }, signal);
        onDone(full, usage);
      } catch (e) { if (!signal?.aborted) onError(e instanceof Error ? e : new Error(String(e))); }
    },
  };
}

function anthropic(apiKey: string): AiProvider {
  return {
    name: 'anthropic',
    async chat(messages, model, onToken, onDone, onError, signal) {
      try {
        const sys = messages.find((m) => m.role === 'system');
        const msgs = messages.filter((m) => m.role !== 'system');
        const res = await fetch('https://api.anthropic.com/v1/messages', {
          method: 'POST',
          headers: {
            'x-api-key': apiKey,
            'anthropic-version': '2023-06-01',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ model, max_tokens: 4096, system: sys?.content, messages: msgs, stream: true }),
          signal,
        });
        if (!res.ok) throw new Error(`Anthropic ${res.status}: ${await res.text()}`);
        let full = '';
        let usage: { tokensIn: number; tokensOut: number } | undefined;
        await readSSE(res, (line) => {
          const j = JSON.parse(line);
          if (j.type === 'content_block_delta' && j.delta?.text) {
            full += j.delta.text;
            onToken(j.delta.text);
          }
          if (j.type === 'message_delta' && j.usage) {
            usage = { tokensIn: j.usage?.input_tokens ?? 0, tokensOut: j.usage.output_tokens };
          }
          if (j.type === 'message_start' && j.message?.usage) {
            usage = { tokensIn: j.message.usage.input_tokens, tokensOut: 0 };
          }
        }, signal);
        onDone(full, usage);
      } catch (e) { if (!signal?.aborted) onError(e instanceof Error ? e : new Error(String(e))); }
    },
  };
}

function ollama(baseUrl = 'http://localhost:11434'): AiProvider {
  return {
    name: 'ollama',
    async chat(messages, model, onToken, onDone, onError, signal) {
      try {
        const res = await fetch(`${baseUrl}/api/chat`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ model, messages, stream: true }),
          signal,
        });
        if (!res.ok) throw new Error(`Ollama ${res.status}`);
        const reader = res.body!.getReader();
        const dec = new TextDecoder();
        let full = '';
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          for (const line of dec.decode(value, { stream: true }).split('\n').filter(Boolean)) {
            const j = JSON.parse(line);
            if (j.message?.content) { full += j.message.content; onToken(j.message.content); }
            if (j.done) { onDone(full, { tokensIn: j.prompt_eval_count ?? 0, tokensOut: j.eval_count ?? 0 }); return; }
          }
        }
        onDone(full);
      } catch (e) { if (!signal?.aborted) onError(e instanceof Error ? e : new Error(String(e))); }
    },
  };
}
