/**
 * AnthropicCanonicalConverter - Converts between provider formats and Anthropic-canonical format
 *
 * Implements DD §12.2; Anthropic-canonical internal format.
 * Must not silently drop semantic prompt/tool info (contracts §23).
 *
 * @module packages/llm/src/canonical/AnthropicCanonical
 */

// Canonical types are defined locally rather than imported from contracts
// to avoid circular dependencies and allow provider-specific extensions.

// =============================================================================
// Canonical Types (Anthropic-canonical internal format)
// =============================================================================

export interface CanonicalMessage {
  role: 'user' | 'assistant' | 'system'
  content: CanonicalContent[]
}

export type CanonicalContent = CanonicalText | CanonicalThinking | CanonicalToolUse | CanonicalToolResult

export interface CanonicalText {
  type: 'text'
  text: string
}

export interface CanonicalThinking {
  type: 'thinking'
  thinking: string
  signature?: string
}

export interface CanonicalToolUse {
  type: 'tool_use'
  id: string
  name: string
  input: Record<string, unknown>
}

export interface CanonicalToolResult {
  type: 'tool_result'
  tool_use_id: string
  content: string
  is_error?: boolean
}

// =============================================================================
// Conversion Report
// =============================================================================

export interface ConversionReport {
  ok: boolean
  dropped_fields: string[]
  warnings: string[]
  input_tokens?: number
  output_tokens?: number
}

// =============================================================================
// AnthropicCanonicalConverter
// =============================================================================

export class AnthropicCanonicalConverter {
  /**
   * Convert from provider format to canonical format.
   * Provider format varies - this handles generic conversion.
   */
  from_provider(provider: string, messages: unknown[]): { canonical: CanonicalMessage[]; report: ConversionReport } {
    const dropped_fields: string[] = []
    const warnings: string[] = []

    const canonical: CanonicalMessage[] = messages.map(msg => {
      if (typeof msg !== 'object' || msg === null) {
        warnings.push('Skipping non-object message')
        return { role: 'user' as const, content: [] }
      }

      const m = msg as Record<string, unknown>
      const role = this.normalize_role(String(m.role || 'user'))

      const content = this.convert_content(m.content, dropped_fields, warnings)

      return { role, content }
    })

    return {
      canonical,
      report: { ok: true, dropped_fields, warnings }
    }
  }

  /**
   * Convert from canonical format to provider format.
   */
  to_provider(provider: string, canonical: CanonicalMessage[]): { messages: unknown[]; report: ConversionReport } {
    const dropped_fields: string[] = []
    const warnings: string[] = []

    const messages = canonical.map(msg => {
      const content = this.convert_content_to_provider(msg.content, provider, dropped_fields, warnings)

      return { role: msg.role, content }
    })

    return {
      messages,
      report: { ok: true, dropped_fields, warnings }
    }
  }

  /**
   * Round-trip test: canonical → provider → canonical
   */
  roundtrip_test(provider: string, canonical: CanonicalMessage[]): { ok: boolean; loss_detected: boolean; report: ConversionReport } {
    const to_provider_result = this.to_provider(provider, canonical)
    const back_to_canonical = this.from_provider(provider, to_provider_result.messages)

    // Check for loss
    const original_json = JSON.stringify(canonical)
    const back_json = JSON.stringify(back_to_canonical.canonical)
    const loss_detected = original_json !== back_json

    return {
      ok: !loss_detected,
      loss_detected,
      report: {
        ok: !loss_detected,
        dropped_fields: [...to_provider_result.report.dropped_fields, ...back_to_canonical.report.dropped_fields],
        warnings: [...to_provider_result.report.warnings, ...back_to_canonical.report.warnings]
      }
    }
  }

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

  private normalize_role(role: string): 'user' | 'assistant' | 'system' {
    const lower = role.toLowerCase()
    if (lower === 'user' || lower === 'human') return 'user'
    if (lower === 'assistant' || lower === 'ai' || lower === 'assistant') return 'assistant'
    return 'system'
  }

  private convert_content(content: unknown, dropped: string[], warnings: string[]): CanonicalContent[] {
    if (!content) return []

    // Handle string content (simple case)
    if (typeof content === 'string') {
      return [{ type: 'text', text: content }]
    }

    // Handle array content
    if (Array.isArray(content)) {
      return content.map(c => this.convert_single_content(c, dropped, warnings)).filter((c): c is CanonicalContent => c !== null)
    }

    // Handle object content
    if (typeof content === 'object') {
      return [this.convert_single_content(content, dropped, warnings)].filter((c): c is CanonicalContent => c !== null)
    }

    warnings.push(`Unknown content type: ${typeof content}`)
    return []
  }

  private convert_single_content(item: unknown, dropped: string[], warnings: string[]): CanonicalContent | null {
    if (typeof item !== 'object' || item === null) {
      return null
    }

    const obj = item as Record<string, unknown>
    const type = String(obj.type || 'text')

    switch (type) {
      case 'text':
        return { type: 'text', text: String(obj.text || '') }

      case 'thinking':
        if (!obj.thinking) {
          warnings.push('Thinking block missing thinking field')
          return null
        }
        return { type: 'thinking', thinking: String(obj.thinking), signature: obj.signature ? String(obj.signature) : undefined }

      case 'tool_use':
        if (!obj.id || !obj.name) {
          warnings.push('ToolUse block missing id or name')
          return null
        }
        return { type: 'tool_use', id: String(obj.id), name: String(obj.name), input: (obj.input as Record<string, unknown>) || {} }

      case 'tool_result':
        if (!obj.tool_use_id && !obj.id) {
          warnings.push('ToolResult missing tool_use_id')
          return null
        }
        return { type: 'tool_result', tool_use_id: String(obj.tool_use_id || obj.id), content: String(obj.content || ''), is_error: Boolean(obj.is_error) }

      default:
        // Unknown type - check if it's a text-like object
        if (obj.text || obj.content) {
          return { type: 'text', text: String(obj.text || obj.content || '') }
        }
        dropped.push(`Unknown content type: ${type}`)
        return null
    }
  }

  private convert_content_to_provider(content: CanonicalContent[], provider: string, dropped: string[], warnings: string[]): unknown[] {
    // Convert canonical content to provider-specific format
    if (provider === 'anthropic') {
      // Anthropic native format
      return content.map(c => {
        switch (c.type) {
          case 'text':
            return { type: 'text', text: c.text }
          case 'thinking':
            return { type: 'thinking', thinking: c.thinking, signature: c.signature }
          case 'tool_use':
            return { type: 'tool_use', id: c.id, name: c.name, input: c.input }
          case 'tool_result':
            return { type: 'tool_result', tool_use_id: c.tool_use_id, content: c.content, is_error: c.is_error }
        }
      })
    }

    // OpenAI-compatible format
    return content.map(c => {
      switch (c.type) {
        case 'text':
          return { type: 'text', text: c.text }
        case 'thinking':
          dropped.push('thinking (not supported in OpenAI format)')
          return { type: 'text', text: `[Thinking: ${c.thinking}]` }
        case 'tool_use':
          return { type: 'tool_use', id: c.id, name: c.name, input: c.input }
        case 'tool_result':
          return { tool_call_id: c.tool_use_id, content: c.content }
      }
    })
  }
}

export function createAnthropicCanonicalConverter(): AnthropicCanonicalConverter {
  return new AnthropicCanonicalConverter()
}