import {
  AuthError,
  FatalError,
  QuotaExhaustedError,
  RateLimitError,
  TransientError,
  type AIContentBlock,
  type AIRequest,
  type AIMessage,
} from './index.js'

export type AnthropicCreds = {
  apiKey: string
  baseUrl?: string
  fetch?: typeof fetch
}

type AnthropicMessage = {
  role: 'user' | 'assistant'
  content: string | Array<{ type: string; text?: string; source?: unknown }>
}

type AnthropicRequest = {
  model: string
  max_tokens: number
  messages: AnthropicMessage[]
  system?: string
  temperature?: number
  stop_sequences?: string[]
}

type AnthropicResponse = {
  model: string
  content: Array<{ type: string; text?: string }>
  stop_reason: string | null
  usage: { input_tokens: number; output_tokens: number }
}

export function extractAnthropicSystem(messages: AIMessage[]): string | undefined {
  const systemParts = messages
    .filter((m) => m.role === 'system')
    .map((m) =>
      typeof m.content === 'string'
        ? m.content
        : m.content
            .filter((b): b is Extract<AIContentBlock, { type: 'text' }> => b.type === 'text')
            .map((b) => b.text)
            .join(''),
    )
  return systemParts.length > 0 ? systemParts.join('\n\n') : undefined
}

export function mapMessagesToAnthropic(messages: AIMessage[]): AnthropicMessage[] {
  return messages
    .filter((m) => m.role !== 'system')
    .map((message) => ({
      role: message.role === 'assistant' ? 'assistant' : 'user',
      content: mapContentToAnthropic(message.content),
    }))
}

export function mapContentToAnthropic(
  content: string | AIContentBlock[],
): string | Array<{ type: string; text?: string; source?: unknown }> {
  if (typeof content === 'string') return content
  return content.map((block) => {
    if (block.type === 'text') {
      return { type: 'text', text: block.text }
    }
    const img = block.image
    return {
      type: 'image',
      source: {
        type: 'base64',
        media_type: img.mediaType,
        data: img.data,
      },
    }
  })
}

export function mapAnthropicResponse(body: AnthropicResponse, provider: string, model: string) {
  const text = body.content
    .filter((part) => part.type === 'text')
    .map((part) => part.text ?? '')
    .join('')
  return {
    content: text,
    model: body.model || model,
    provider,
    usage: {
      promptTokens: body.usage.input_tokens,
      completionTokens: body.usage.output_tokens,
    },
    finishReason: body.stop_reason ?? undefined,
  }
}

export function classifyAnthropicError(status: number, message: string, provider: string) {
  if (status === 429) {
    if (/quota/i.test(message)) {
      return new QuotaExhaustedError(message, provider)
    }
    return new RateLimitError(message, provider)
  }
  if (status === 401 || status === 403) {
    return new AuthError(message, provider)
  }
  if (status >= 500) {
    return new TransientError(message, provider)
  }
  return new FatalError(message, provider)
}

export function makeAnthropicAdapter(creds: AnthropicCreds) {
  const baseUrl = creds.baseUrl ?? 'https://api.anthropic.com/v1'
  const fetchImpl = creds.fetch ?? fetch

  return {
    provider: 'anthropic',
    supportsImage: true,
    async call(req: AIRequest) {
      const system = extractAnthropicSystem(req.messages)
      const payload: AnthropicRequest = {
        model: req.model,
        max_tokens: req.maxTokens ?? 1024,
        messages: mapMessagesToAnthropic(req.messages),
        ...(system ? { system } : {}),
        ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),
        ...(req.stop !== undefined
          ? {
              stop_sequences: Array.isArray(req.stop) ? req.stop : [req.stop],
            }
          : {}),
      }

      const response = await fetchImpl(`${baseUrl}/messages`, {
        method: 'POST',
        headers: {
          'x-api-key': creds.apiKey,
          'anthropic-version': '2023-06-01',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
      })

      const text = await response.text()
      let body: unknown
      try {
        body = JSON.parse(text)
      } catch {
        throw new TransientError(`Invalid JSON from provider: ${text.slice(0, 120)}`, 'anthropic')
      }

      if (!response.ok) {
        const message =
          typeof body === 'object' &&
          body !== null &&
          'error' in body &&
          typeof (body as { error?: { message?: string } }).error?.message === 'string'
            ? (body as { error: { message: string } }).error.message
            : text
        throw classifyAnthropicError(response.status, message, 'anthropic')
      }

      return mapAnthropicResponse(body as AnthropicResponse, 'anthropic', req.model)
    },
  }
}
