/**
 * AnthropicAdapter - Provider adapter for Anthropic API
 *
 * Implements ProviderAdapter contract (contracts §15); DD §12.2.
 *
 * @module packages/llm/src/adapters/AnthropicAdapter
 */

import type { CanonicalMessage } from '../canonical/AnthropicCanonical.js'
import { AnthropicCanonicalConverter } from '../canonical/AnthropicCanonical.js'

// Local type definitions (contract types not yet finalized)
type CompleteOptions = { max_tokens?: number; temperature?: number; top_p?: number; system?: string }
type StreamEvent = { type: 'text' | 'thinking' | 'done'; content?: string; reason?: string }
type ModelRequirement = { model: string; provider?: string; min_output_tokens?: number; prefers_thinking?: boolean; requires_tools?: boolean }

export interface AnthropicConfig {
  api_key?: string
  base_url?: string
  max_retries?: number
  timeout?: number
}

// Provider stream events
export type AnthropicStreamEvent =
  | { type: 'content_block_start'; index: number; block_type: string }
  | { type: 'content_block_delta'; index: number; delta: { type: string; text?: string; thinking?: string } }
  | { type: 'content_block_stop'; index: number }
  | { type: 'message_start'; message: { id: string; type: string; role: string; content: unknown[] } }
  | { type: 'message_delta'; delta: { stop_reason?: string; usage?: { output_tokens: number } } }
  | { type: 'message_stop' }

export class AnthropicAdapter {
  private api_key: string
  private base_url: string
  private max_retries: number
  private timeout: number
  private converter: AnthropicCanonicalConverter

  constructor(config: AnthropicConfig = {}) {
    this.api_key = config.api_key || process.env.ANTHROPIC_API_KEY || ''
    this.base_url = config.base_url || process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com'
    this.max_retries = config.max_retries || 3
    this.timeout = config.timeout || 60000
    this.converter = new AnthropicCanonicalConverter()
  }

  async list_models(): Promise<string[]> {
    // Anthropic doesn't have a list_models API, return known models
    return [
      'claude-opus-4-7-20251119',
      'claude-sonnet-4-6-20250501',
      'claude-haiku-4-5-20251001'
    ]
  }

  async validate_model(model: string): Promise<{ valid: boolean; error?: string }> {
    const known = await this.list_models()
    // Allow any model that looks like a Claude model
    if (model.startsWith('claude-')) {
      return { valid: true }
    }
    // Or check known list
    if (known.includes(model)) {
      return { valid: true }
    }
    return { valid: false, error: `Unknown model: ${model}` }
  }

  async complete(
    messages: CanonicalMessage[],
    requirement: ModelRequirement,
    options: CompleteOptions = {}
  ): Promise<{ content: string; usage?: { input_tokens: number; output_tokens: number } }> {
    const { canonical, report } = this.converter.from_provider('anthropic', messages as unknown[])

    if (!report.ok) {
      throw new Error(`Conversion failed: ${report.warnings.join(', ')}`)
    }

    const response = await this.make_request({
      model: requirement.model,
      messages: canonical.map(m => ({
        role: m.role,
        content: m.content.map(c => {
          if (c.type === 'text') return { type: 'text', text: c.text }
          if (c.type === 'thinking') return { type: 'thinking', thinking: c.thinking }
          if (c.type === 'tool_use') return { type: 'tool_use', id: c.id, name: c.name, input: c.input }
          return { type: 'text', text: '[tool]' }
        })
      })),
      max_tokens: options.max_tokens || 4096,
      temperature: options.temperature,
      top_p: options.top_p,
      system: options.system,
      stream: false
    })

    // Extract content from response
    const content = this.extract_content(response)
    const usage = response.usage ? { input_tokens: response.usage.input_tokens, output_tokens: response.usage.output_tokens } : undefined

    return { content, usage }
  }

  async *stream_complete(
    messages: CanonicalMessage[],
    requirement: ModelRequirement,
    options: CompleteOptions = {}
  ): AsyncGenerator<StreamEvent> {
    const { canonical, report } = this.converter.from_provider('anthropic', messages as unknown[])

    if (!report.ok) {
      throw new Error(`Conversion failed: ${report.warnings.join(', ')}`)
    }

    const response = await this.make_request({
      model: requirement.model,
      messages: canonical.map(m => ({
        role: m.role,
        content: m.content.map(c => {
          if (c.type === 'text') return { type: 'text', text: c.text }
          if (c.type === 'thinking') return { type: 'thinking', thinking: c.thinking }
          if (c.type === 'tool_use') return { type: 'tool_use', id: c.id, name: c.name, input: c.input }
          return { type: 'text', text: '[tool]' }
        })
      })),
      max_tokens: options.max_tokens || 4096,
      temperature: options.temperature,
      top_p: options.top_p,
      system: options.system,
      stream: true
    })

    // Parse streaming response
    const reader = response.body?.getReader()
    if (!reader) {
      throw new Error('No response body')
    }

    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      for (const line of lines) {
        if (!line.trim() || !line.startsWith('data: ')) continue

        const data = line.slice(6)
        if (data === '[DONE]') continue

        try {
          const event = JSON.parse(data) as AnthropicStreamEvent
          yield this.normalize_stream_event(event)
        } catch {
          // Skip invalid JSON
        }
      }
    }
  }

  async count_tokens(text: string): Promise<number> {
    // Simple estimation - in production use proper tokenization
    return Math.ceil(text.length / 4)
  }

  // ============================================================================
  // Private helpers
  // ============================================================================

  private async make_request(body: Record<string, unknown>): Promise<Record<string, unknown>> {
    const url = `${this.base_url}/v1/messages`

    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': this.api_key,
        'anthropic-version': '2023-06-01'
      },
      body: JSON.stringify(body)
    })

    if (!response.ok) {
      const error = await response.text()
      throw new Error(`Anthropic API error: ${response.status} - ${error}`)
    }

    return response.json() as Promise<Record<string, unknown>>
  }

  private extract_content(response: Record<string, unknown>): string {
    const content = response.content as Array<{ type: string; text?: string }> | undefined
    if (!content) return ''

    return content
      .filter((b) => b.type === 'text')
      .map((b) => b.text || '')
      .join('')
  }

  private normalize_stream_event(event: AnthropicStreamEvent): StreamEvent {
    switch (event.type) {
      case 'content_block_delta':
        if (event.delta.type === 'text_delta') {
          return { type: 'text', content: event.delta.text || '' }
        }
        if (event.delta.type === 'thinking_delta') {
          return { type: 'thinking', content: event.delta.thinking || '' }
        }
        return { type: 'text', content: '' }

      case 'message_delta':
        if (event.delta.stop_reason) {
          return { type: 'done', reason: event.delta.stop_reason }
        }
        return { type: 'text', content: '' }

      default:
        return { type: 'text', content: '' }
    }
  }
}

export function createAnthropicAdapter(config?: AnthropicConfig): AnthropicAdapter {
  return new AnthropicAdapter(config)
}