/* real-models.jsx — direct browser API clients for AI providers.
 *
 * All calls run client-side. Keys live in localStorage. Anything CORS-blocked
 * is reported as such rather than silently failing.
 */

const PROVIDERS = [
  {
    id: 'openai', name: 'OpenAI', label: 'GPT',
    model: 'gpt-5',
    placeholder: 'sk-proj-...',
    docsUrl: 'https://platform.openai.com/api-keys',
    cors: 'ok',
    hue: 158,
  },
  {
    id: 'anthropic', name: 'Anthropic', label: 'Claude',
    model: 'claude-sonnet-4-5',
    placeholder: 'sk-ant-...',
    docsUrl: 'https://console.anthropic.com/settings/keys',
    cors: 'ok',
    hue: 28,
  },
  {
    id: 'google', name: 'Google', label: 'Gemini',
    model: 'gemini-2.5-pro',
    placeholder: 'AIza...',
    docsUrl: 'https://aistudio.google.com/apikey',
    cors: 'ok',
    hue: 278,
  },
  {
    id: 'perplexity', name: 'Perplexity', label: 'Sonar',
    model: 'sonar-pro',
    placeholder: 'pplx-...',
    docsUrl: 'https://docs.perplexity.ai/guides/getting-started',
    cors: 'partial',
    hue: 218,
  },
  {
    id: 'xai', name: 'xAI', label: 'Grok',
    model: 'grok-4',
    placeholder: 'xai-...',
    docsUrl: 'https://console.x.ai',
    cors: 'partial',
    hue: 358,
  },
  {
    id: 'deepseek', name: 'DeepSeek', label: 'DeepSeek',
    model: 'deepseek-v3.1',
    placeholder: 'sk-...',
    docsUrl: 'https://platform.deepseek.com/api_keys',
    cors: 'proxy',
    hue: 318,
  },
];

const PROVIDER_BY_ID = Object.fromEntries(PROVIDERS.map(p => [p.id, p]));

async function timedFetch(url, opts, ms = 25000) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { ...opts, signal: ctrl.signal });
  } finally {
    clearTimeout(t);
  }
}

/* callModel — primary entry point for any LLM call from the browser.
 *
 * When window.AISO_API_BASE is set (managed mode, server proxies + env-var keys):
 *   - The userKey argument is IGNORED. Server uses its env-var keys.
 * When AISO_API_BASE is unset (BYO sandbox mode):
 *   - Falls through to direct provider calls using the user-pasted key.
 */
async function callModel(providerId, userKey, prompt, opts = {}) {
  if (typeof window !== 'undefined' && window.aisoEnabled && window.aisoEnabled()) {
    return window.aisoCall(providerId, prompt, opts);
  }
  return _callModelDirect(providerId, userKey, prompt, opts);
}

async function _callModelDirect(providerId, apiKey, prompt, { maxTokens = 400 } = {}) {
  if (!apiKey) throw new Error('no key configured');
  const p = PROVIDER_BY_ID[providerId];
  if (!p) throw new Error('unknown provider: ' + providerId);

  if (providerId === 'openai') {
    const r = await timedFetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: p.model,
        messages: [{ role: 'user', content: prompt }],
        max_tokens: maxTokens,
      }),
    });
    if (!r.ok) throw new Error(`OpenAI ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    return { text: data.choices?.[0]?.message?.content || '', raw: data, model: p.model };
  }

  if (providerId === 'anthropic') {
    const r = await timedFetch('https://api.anthropic.com/v1/messages', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01',
        'anthropic-dangerous-direct-browser-access': 'true',
      },
      body: JSON.stringify({
        model: p.model,
        max_tokens: maxTokens,
        messages: [{ role: 'user', content: prompt }],
      }),
    });
    if (!r.ok) throw new Error(`Anthropic ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    const text = (data.content || []).map(c => c.text || '').join('');
    return { text, raw: data, model: p.model };
  }

  if (providerId === 'google') {
    const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(p.model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
    const r = await timedFetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contents: [{ parts: [{ text: prompt }] }],
        generationConfig: { maxOutputTokens: maxTokens },
      }),
    });
    if (!r.ok) throw new Error(`Google ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    const text = (data.candidates?.[0]?.content?.parts || []).map(part => part.text || '').join('');
    return { text, raw: data, model: p.model };
  }

  if (providerId === 'perplexity') {
    const r = await timedFetch('https://api.perplexity.ai/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: p.model,
        messages: [{ role: 'user', content: prompt }],
        max_tokens: maxTokens,
      }),
    });
    if (!r.ok) throw new Error(`Perplexity ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    return { text: data.choices?.[0]?.message?.content || '', raw: data, model: p.model };
  }

  if (providerId === 'xai') {
    const r = await timedFetch('https://api.x.ai/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: p.model,
        messages: [{ role: 'user', content: prompt }],
        max_tokens: maxTokens,
      }),
    });
    if (!r.ok) throw new Error(`xAI ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    return { text: data.choices?.[0]?.message?.content || '', raw: data, model: p.model };
  }

  if (providerId === 'deepseek') {
    // DeepSeek currently doesn't return CORS headers on api.deepseek.com.
    // We try anyway; many users will hit a CORS error reported as "Failed to fetch".
    const r = await timedFetch('https://api.deepseek.com/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: p.model,
        messages: [{ role: 'user', content: prompt }],
        max_tokens: maxTokens,
      }),
    });
    if (!r.ok) throw new Error(`DeepSeek ${r.status}: ${(await r.text()).slice(0, 200)}`);
    const data = await r.json();
    return { text: data.choices?.[0]?.message?.content || '', raw: data, model: p.model };
  }

  throw new Error('unknown provider ' + providerId);
}

/* parse a model's free-text answer for whether the brand was mentioned, and
 * roughly where in the answer.
 */
function parseMention(text, brand, domain) {
  if (!text) return { mentioned: false, position: null, length: 0 };
  const lower = text.toLowerCase();
  const b = (brand || '').toLowerCase();
  const d = (domain || '').toLowerCase().replace(/^https?:\/\//, '');
  const bareDomain = d.split('.')[0];
  let idx = -1;
  if (b && lower.includes(b)) idx = lower.indexOf(b);
  else if (d && lower.includes(d)) idx = lower.indexOf(d);
  else if (bareDomain && lower.includes(bareDomain)) idx = lower.indexOf(bareDomain);
  if (idx < 0) return { mentioned: false, position: null, length: text.length };
  // rough rank: which "item" of a list does the mention appear in?
  const before = text.slice(0, idx);
  const listMatches = before.match(/(?:^|\n)\s*(?:\d+[\.\)]|[•\-\*])/g) || [];
  return {
    mentioned: true,
    position: idx,
    rank: listMatches.length + 1,
    length: text.length,
    snippet: text.slice(Math.max(0, idx - 30), Math.min(text.length, idx + 80)),
  };
}

Object.assign(window, { PROVIDERS, PROVIDER_BY_ID, callModel, parseMention });
