/**
 * OpenAIEmbeddingProvider — direct-to-OpenAI BYOK path.
 *
 * Unlike AzureEmbeddingProvider (which goes through the Loxia backend),
 * this provider hits `https://api.openai.com/v1/embeddings` directly using
 * the user's own OpenAI key from ApiKeyManager.vendorKeys.openai.
 *
 * Why both providers: some users want to bring their own OpenAI key
 * rather than route through Loxia's backend. The Loxia backend doesn't
 * have to be involved for them. Cost still lands on their OpenAI account.
 *
 * Same request/response shape as Azure (it's the OpenAI API in both cases);
 * the difference is the URL + Authorization header.
 */

import { EmbeddingProvider, EmbeddingError, EMBEDDING_ERROR_CODES } from '../providerInterface.js';
import { postEmbeddings, extractOpenAIShape } from './cloudHttpTransport.js';

const DEFAULT_URL = 'https://api.openai.com/v1/embeddings';
const DEFAULT_MODEL = 'text-embedding-3-small';
const DEFAULT_DIMS = 1536;
const DEFAULT_MAX_BATCH = 16;
const DEFAULT_MAX_TOKENS = 8191;

export class OpenAIEmbeddingProvider extends EmbeddingProvider {
  /**
   * @param {object} opts
   * @param {() => string|null} opts.getApiKey  - Returns current OpenAI key.
   *        Callback (not stored string) so key rotation in the
   *        ApiKeyManager is reflected without re-instantiating.
   * @param {string} [opts.url=DEFAULT_URL] - Override for compat-layer
   *        proxies (DeepInfra, Together, etc.) that mimic OpenAI's API.
   * @param {string} [opts.modelId]
   * @param {number} [opts.dimensions]
   * @param {number} [opts.maxBatchSize]
   * @param {number} [opts.timeoutMs]
   * @param {typeof fetch} [opts.fetchImpl] - Injectable for tests.
   */
  constructor({
    getApiKey,
    url = DEFAULT_URL,
    modelId = DEFAULT_MODEL,
    dimensions = DEFAULT_DIMS,
    maxBatchSize = DEFAULT_MAX_BATCH,
    timeoutMs,
    fetchImpl,
  } = {}) {
    super();
    if (typeof getApiKey !== 'function') {
      throw new Error('OpenAIEmbeddingProvider requires a getApiKey() callback');
    }
    this._url = url;
    this._getApiKey = getApiKey;
    this._modelId = modelId;
    this._dimensions = dimensions;
    this._maxBatchSize = maxBatchSize;
    this._timeoutMs = timeoutMs;
    this._fetchImpl = fetchImpl;
  }

  getInfo() {
    return {
      name: 'openai',
      modelId: this._modelId,
      dimensions: this._dimensions,
      maxBatchSize: this._maxBatchSize,
      maxTokensPerText: DEFAULT_MAX_TOKENS,
      distinguishesQueryAndDocument: false,
      isLocal: false,
    };
  }

  async health() {
    const key = this._getApiKey();
    if (!key) return { ok: false, reason: 'No OpenAI API key set. Add one under Settings → API Keys.' };
    return { ok: true };
  }

  async _embed(texts, _kind) {
    const apiKey = this._getApiKey();
    if (!apiKey) {
      throw new EmbeddingError(
        EMBEDDING_ERROR_CODES.AUTH_ERROR,
        'No OpenAI API key set. Add one under Settings → API Keys.',
        { provider: 'openai', transient: false },
      );
    }
    return postEmbeddings({
      url: this._url,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: { input: texts, model: this._modelId },
      providerName: 'openai',
      extractVectors: extractOpenAIShape,
      timeoutMs: this._timeoutMs,
      fetchImpl: this._fetchImpl,
    });
  }
}
