/**
 * Anthropic Claude adapter — system-ai.
 * Uses @anthropic-ai/sdk; maps AIContentBlock images to Claude Vision image blocks.
 */
import Anthropic from '@anthropic-ai/sdk'
import type { AIAdapter, AIContentBlock, AIMessage, AIRequest, AIResponse } from '../adapter'
import { AIProviderError } from '../errors'
import type { AIEnv } from './index'

function mapContentBlocks(
  content: string | AIContentBlock[],
): Anthropic.MessageParam['content'] {
  if (typeof content === 'string') return content
  return content
    .filter((b): b is AIContentBlock => b.type === 'text' || b.type === 'image')
    .map((b) => {
      if (b.type === 'text') {
        return { type: 'text' as const, text: b.text ?? '' }
      }
      // image block → Claude Vision
      const img = b.image!
      return {
        type: 'image' as const,
        source: {
          type: 'base64' as const,
          media_type: img.mediaType as 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp',
          data: img.data,
        },
      }
    })
}

function mapMessages(messages: AIMessage[]): Anthropic.MessageParam[] {
  // Anthropic separates system messages; filter them here so we only send user/assistant
  return messages
    .filter((m) => m.role !== 'system')
    .map((m) => ({
      role: m.role as 'user' | 'assistant',
      content: mapContentBlocks(m.content),
    }))
}

function extractSystem(messages: AIMessage[]): string | undefined {
  const sys = messages.filter((m) => m.role === 'system')
  if (sys.length === 0) return undefined
  return sys
    .map((m) => (typeof m.content === 'string' ? m.content : m.content.map((b) => b.text ?? '').join('')))
    .join('\n\n')
}

function normalizeError(err: unknown, provider: string): AIProviderError {
  if (err instanceof Anthropic.APIError) {
    const status = err.status
    if (status === 429) {
      return new AIProviderError('rate_limit', provider, true, `Rate limited: ${err.message}`)
    }
    if (status === 401 || status === 403) {
      return new AIProviderError('auth_error', provider, false, `Auth error: ${err.message}`)
    }
    if (status === 404) {
      return new AIProviderError('model_unavailable', provider, true, `Model unavailable: ${err.message}`)
    }
    return new AIProviderError('unknown', provider, false, `API error ${status}: ${err.message}`)
  }
  if (err instanceof Error && (err.name === 'AbortError' || err.message.includes('timeout'))) {
    return new AIProviderError('timeout', provider, true, `Request timed out: ${err.message}`)
  }
  const msg = err instanceof Error ? err.message : String(err)
  return new AIProviderError('unknown', provider, false, `Unknown error: ${msg}`)
}

export class AnthropicAdapter implements AIAdapter {
  readonly provider = 'anthropic' as const
  private client: Anthropic

  constructor(env: AIEnv) {
    this.client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY })
  }

  async call(request: AIRequest): Promise<AIResponse> {
    const start = Date.now()
    try {
      const systemText = extractSystem(request.messages)
      const anthropicMessages = mapMessages(request.messages)

      const response = await this.client.messages.create({
        model: request.model,
        max_tokens: request.maxTokens ?? 1024,
        ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
        ...(systemText ? { system: systemText } : {}),
        messages: anthropicMessages,
      })

      const content = response.content
        .filter((b): b is Anthropic.TextBlock => b.type === 'text')
        .map((b) => b.text)
        .join('')

      return {
        content,
        inputTokens: response.usage.input_tokens,
        outputTokens: response.usage.output_tokens,
        model: request.model,
        provider: this.provider,
        durationMs: Date.now() - start,
      }
    } catch (err) {
      throw normalizeError(err, this.provider)
    }
  }

  stream(request: AIRequest): ReadableStream<string> {
    const self = this
    return new ReadableStream<string>({
      async start(controller) {
        const start = Date.now()
        try {
          const systemText = extractSystem(request.messages)
          const anthropicMessages = mapMessages(request.messages)

          const stream = await self.client.messages.stream({
            model: request.model,
            max_tokens: request.maxTokens ?? 1024,
            ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
            ...(systemText ? { system: systemText } : {}),
            messages: anthropicMessages,
          })

          for await (const chunk of stream) {
            if (
              chunk.type === 'content_block_delta' &&
              chunk.delta.type === 'text_delta'
            ) {
              controller.enqueue(chunk.delta.text)
            }
          }
          controller.close()
        } catch (err) {
          controller.error(normalizeError(err, self.provider))
        }
        void start // suppress unused-var lint
      },
    })
  }
}
