/**
 * Multi-Provider Factory
 * Ported from opencode-ai/opencode internal/llm/provider/provider.go
 *
 * Unified interface for all AI providers. Most providers reuse the
 * OpenAI-compatible client with different base URLs (opencode pattern).
 * Only Anthropic and Google need native SDKs.
 *
 * This factory abstracts provider differences so the caller only
 * needs to call streamChat(messages, options) regardless of provider.
 */

// OpenAI-compatible endpoints (opencode pattern: one client, many providers)
const OPENAI_COMPATIBLE_PROVIDERS = {
  openrouter: {
    baseUrl: 'https://openrouter.ai/api/v1',
    keyEnv: 'OPENROUTER_API_KEY',
    extraHeaders: (model) => ({
      'HTTP-Referer': 'https://cli.upfyn.com',
      'X-Title': 'Upfyn-Code',
    }),
  },
  groq: {
    baseUrl: 'https://api.groq.com/openai/v1',
    keyEnv: 'GROQ_API_KEY',
  },
  deepseek: {
    baseUrl: 'https://api.deepseek.com/v1',
    keyEnv: 'DEEPSEEK_API_KEY',
  },
  xai: {
    baseUrl: 'https://api.x.ai/v1',
    keyEnv: 'XAI_API_KEY',
  },
  openai: {
    baseUrl: 'https://api.openai.com/v1',
    keyEnv: 'OPENAI_API_KEY',
  },
};

// Cost per 1M tokens (opencode pattern: models.go cost tables)
const MODEL_COSTS = {
  'anthropic/claude-sonnet-4': { input: 3.0, output: 15.0 },
  'anthropic/claude-opus-4': { input: 15.0, output: 75.0 },
  'openai/gpt-4o': { input: 2.5, output: 10.0 },
  'openai/gpt-4o-mini': { input: 0.15, output: 0.6 },
  'openai/o3': { input: 10.0, output: 40.0 },
  'google/gemini-2.5-pro': { input: 1.25, output: 10.0 },
  'deepseek/deepseek-r1': { input: 0.55, output: 2.19 },
  'meta-llama/llama-4-maverick': { input: 0.5, output: 0.7 },
};

/**
 * Detect which provider to use based on model name.
 * @param {string} model - Model name (e.g., 'anthropic/claude-sonnet-4')
 * @returns {string} Provider name
 */
export function detectProvider(model) {
  if (!model) return 'openrouter';
  if (model.startsWith('anthropic/')) return 'openrouter'; // route through OpenRouter for BYOK
  if (model.startsWith('openai/')) return 'openrouter';
  if (model.startsWith('google/')) return 'openrouter';
  if (model.startsWith('meta-llama/')) return 'openrouter';
  if (model.startsWith('deepseek/')) return 'openrouter';
  if (model.startsWith('mistralai/')) return 'openrouter';
  // Direct provider keys
  if (model.startsWith('gpt-')) return 'openai';
  if (model.startsWith('claude-')) return 'anthropic';
  return 'openrouter';
}

/**
 * Create a streaming chat request to any provider.
 * Uses the OpenAI-compatible API for most providers (opencode pattern).
 *
 * @param {string} model - Model name
 * @param {Array} messages - Array of { role, content }
 * @param {object} options - { apiKey, temperature, maxTokens }
 * @returns {AsyncGenerator} Yields { type, content, usage } events
 */
export async function* streamChat(model, messages, options = {}) {
  const provider = detectProvider(model);
  const config = OPENAI_COMPATIBLE_PROVIDERS[provider];

  if (!config) {
    throw new Error(`Unsupported provider: ${provider}`);
  }

  const apiKey = options.apiKey || process.env[config.keyEnv];
  if (!apiKey) {
    throw new Error(`API key required for ${provider}. Set ${config.keyEnv} or provide via BYOK settings.`);
  }

  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${apiKey}`,
    ...(config.extraHeaders ? config.extraHeaders(model) : {}),
  };

  const body = {
    model,
    messages,
    stream: true,
    ...(options.temperature !== undefined && { temperature: options.temperature }),
    ...(options.maxTokens && { max_tokens: options.maxTokens }),
  };

  const response = await fetch(`${config.baseUrl}/chat/completions`, {
    method: 'POST',
    headers,
    body: JSON.stringify(body),
  });

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

  // Parse SSE stream
  const reader = response.body.getReader();
  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.startsWith('data: ')) continue;
      const data = line.slice(6).trim();
      if (data === '[DONE]') {
        yield { type: 'complete' };
        return;
      }

      try {
        const parsed = JSON.parse(data);
        const delta = parsed.choices?.[0]?.delta;

        if (delta?.content) {
          yield { type: 'content_delta', content: delta.content };
        }

        // Usage info (if provided at the end)
        if (parsed.usage) {
          yield {
            type: 'usage',
            promptTokens: parsed.usage.prompt_tokens || 0,
            completionTokens: parsed.usage.completion_tokens || 0,
          };
        }
      } catch {
        // Skip malformed SSE chunks
      }
    }
  }
}

/**
 * Calculate cost for a request.
 * @param {string} model - Model name
 * @param {number} promptTokens
 * @param {number} completionTokens
 * @returns {number} Cost in cents
 */
export function calculateCost(model, promptTokens, completionTokens) {
  const costs = MODEL_COSTS[model];
  if (!costs) return 0;
  return (promptTokens / 1_000_000 * costs.input + completionTokens / 1_000_000 * costs.output) * 100;
}
