import type { GenerateChunk, GenerateOpts, LLMProvider } from '../types';
import { sseLines } from '../sse';

/**
 * Anthropic Claude: SSE over POST /v1/messages.
 *
 * Streaming events:
 *   - message_start                          → has usage.input_tokens
 *   - content_block_delta (type=text_delta)  → has delta.text
 *   - message_delta                          → has usage.output_tokens
 *   - message_stop                           → terminal
 *   - error                                  → fatal
 */
export const claude: LLMProvider = {
  id: 'claude',
  label: 'Anthropic Claude',
  requiresApiKey: true,
  requiresBaseUrl: false,

  async *generate(opts: GenerateOpts): AsyncGenerator<GenerateChunk, void, void> {
    if (!opts.apiKey) {
      yield { type: 'error', message: 'Claude API key is required.' };
      return;
    }
    const url = 'https://api.anthropic.com/v1/messages';
    let res: Response;
    try {
      res = await fetch(url, {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          'x-api-key': opts.apiKey,
          'anthropic-version': '2023-06-01',
        },
        body: JSON.stringify({
          model: opts.model,
          max_tokens: opts.maxOutputTokens ?? 8000,
          stream: true,
          system: opts.systemPrompt,
          messages: [{ role: 'user', content: opts.userPrompt }],
        }),
        signal: opts.signal,
      });
    } catch (e) {
      yield {
        type: 'error',
        message: `Claude unreachable: ${(e as Error).message}`,
      };
      return;
    }
    if (!res.ok) {
      yield {
        type: 'error',
        message: `Claude HTTP ${res.status}: ${(await res.text().catch(() => '')).slice(0, 500)}`,
      };
      return;
    }
    let tokensIn: number | undefined;
    let tokensOut: number | undefined;
    try {
      for await (const data of sseLines(res)) {
        if (data === '[DONE]') break;
        let evt: any;
        try {
          evt = JSON.parse(data);
        } catch {
          continue;
        }
        switch (evt.type) {
          case 'message_start':
            tokensIn = evt.message?.usage?.input_tokens;
            break;
          case 'content_block_delta':
            if (evt.delta?.type === 'text_delta' && evt.delta.text) {
              yield { type: 'text', delta: evt.delta.text };
            }
            break;
          case 'message_delta':
            if (evt.usage?.output_tokens != null) {
              tokensOut = evt.usage.output_tokens;
            }
            break;
          case 'message_stop':
            // terminal — fall through to outer break
            break;
          case 'error':
            yield {
              type: 'error',
              message: evt.error?.message ?? 'Claude stream error',
            };
            return;
        }
      }
      yield { type: 'usage', tokensIn, tokensOut };
      yield { type: 'done' };
    } catch (e) {
      yield {
        type: 'error',
        message: `Claude stream error: ${(e as Error).message}`,
      };
    }
  },
};
