/**
 * ProviderManager - Unified facade for LLM providers
 *
 * Implements contracts §15; DD §12.1.
 * INV-4: runtime calls llm only via this facade.
 *
 * @module packages/llm/src/ProviderManager
 */

// 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 }
type ModelAssignment = { provider: string; model: string; adapter: any; capabilities?: any }

import { ModelConfigLoader, createModelConfigLoader } from './ModelConfigLoader.js'
import { CapabilityMatrixRegistry, createCapabilityMatrixRegistry } from './CapabilityMatrix.js'
import { AnthropicAdapter, createAnthropicAdapter } from './adapters/AnthropicAdapter.js'
import { OpenAICompatibleAdapter, createOpenAICompatibleAdapter } from './adapters/OpenAICompatibleAdapter.js'

export interface ProviderManagerConfig {
  config_loader?: ModelConfigLoader
  capability_matrix?: CapabilityMatrixRegistry
}

export class ProviderManager {
  private config_loader: ModelConfigLoader
  private capability_matrix: CapabilityMatrixRegistry
  private adapters: Map<string, ProviderAdapter> = new Map()
  private current_adapter: ProviderAdapter | null = null
  private current_model: string = ''

  constructor(config: ProviderManagerConfig = {}) {
    this.config_loader = config.config_loader || createModelConfigLoader()
    this.capability_matrix = config.capability_matrix || createCapabilityMatrixRegistry()

    // Initialize adapters
    this.initialize_adapters()
  }

  /**
   * Load configuration from files.
   */
  load_config(): { ok: boolean; error?: string } {
    const result = this.config_loader.load()
    if (!result.ok) {
      return { ok: false, error: result.error }
    }
    return { ok: true }
  }

  /**
   * Select a model based on requirements.
   * Returns a ModelAssignment.
   */
  select_model(requirement: ModelRequirement): ModelAssignment {
    // Try to find matching model in capability matrix
    const best_model = this.capability_matrix.find_best(requirement.provider || 'anthropic', {
      min_output_tokens: requirement.min_output_tokens,
      supports_thinking: requirement.prefers_thinking,
      supports_tools: requirement.requires_tools
    })

    const model = requirement.model || best_model || `${requirement.provider}-default`

    // Get adapter for this provider
    const adapter = this.get_or_create_adapter(requirement.provider || 'anthropic', model)
    this.current_adapter = adapter
    this.current_model = model

    return {
      provider: requirement.provider || 'anthropic',
      model,
      adapter,
      capabilities: this.capability_matrix.lookup(requirement.provider || 'anthropic', model)
    }
  }

  /**
   * Complete a request with the current model.
   */
  async complete(
    messages: unknown[],
    assignment: ModelAssignment,
    options: CompleteOptions = {}
  ): Promise<{ content: string; usage?: { input_tokens: number; output_tokens: number } }> {
    const adapter = assignment.adapter || this.current_adapter
    if (!adapter) {
      throw new Error('No adapter selected. Call select_model first.')
    }

    return adapter.complete(messages as any, { model: assignment.model }, options)
  }

  /**
   * Stream a completion request.
   */
  async *stream_complete(
    messages: unknown[],
    assignment: ModelAssignment,
    options: CompleteOptions = {}
  ): AsyncGenerator<StreamEvent> {
    const adapter = assignment.adapter || this.current_adapter
    if (!adapter) {
      throw new Error('No adapter selected. Call select_model first.')
    }

    yield* adapter.stream_complete(messages as any, { model: assignment.model }, options)
  }

  /**
   * Get an adapter for a specific provider.
   */
  adapter_for(provider: string, model: string): ProviderAdapter {
    return this.get_or_create_adapter(provider, model)
  }

  /**
   * Get current adapter.
   */
  get_current_adapter(): ProviderAdapter | null {
    return this.current_adapter
  }

  /**
   * Get current model.
   */
  get_current_model(): string {
    return this.current_model
  }

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

  private initialize_adapters(): void {
    // Create default adapters
    const anthropic = createAnthropicAdapter()
    this.adapters.set('anthropic', anthropic)

    // Check for OpenAI-compatible providers in config
    const config = this.config_loader.load()
    if (config.ok && config.configs?.global) {
      for (const [name, model_config] of Object.entries(config.configs.global)) {
        if (model_config.provider === 'openai-compatible' && model_config.base_url) {
          const adapter = createOpenAICompatibleAdapter({
            base_url: model_config.base_url,
            model: model_config.model,
            api_key: model_config.api_key
          })
          this.adapters.set(name, adapter)
        }
      }
    }
  }

  private get_or_create_adapter(provider: string, model: string): ProviderAdapter {
    // Check if we already have an adapter for this provider
    const existing = this.adapters.get(provider)
    if (existing) {
      return existing
    }

    // Create new adapter based on provider
    let adapter: ProviderAdapter

    if (provider === 'anthropic') {
      adapter = createAnthropicAdapter()
    } else {
      // Check config for OpenAI-compatible
      const model_config = this.config_loader.get_model(`${provider}-${model}`)
      if (model_config?.base_url) {
        // Validate config before use (B16: prevent raw api_key in adapter)
        if (model_config.api_key && !model_config.auth_ref) {
          this.config_loader.validate(model_config)
          console.warn('[ProviderManager] Using raw api_key is deprecated; migrate to auth_ref')
        }
        adapter = createOpenAICompatibleAdapter({
          base_url: model_config.base_url,
          model: model_config.model,
          api_key: model_config.api_key
        })
      } else {
        // Default to OpenAI-compatible with default settings
        adapter = createOpenAICompatibleAdapter({
          base_url: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
          model: model
        })
      }
    }

    this.adapters.set(provider, adapter)
    return adapter
  }
}

export function createProviderManager(config?: ProviderManagerConfig): ProviderManager {
  return new ProviderManager(config)
}

// Export facade as default instance
let default_instance: ProviderManager | undefined

export function get_provider_manager(): ProviderManager {
  if (!default_instance) {
    default_instance = createProviderManager()
  }
  return default_instance
}