/**
 * AzureCustomEmbeddingProvider — point Loxia at YOUR Azure AI Foundry
 * OpenAI-compatible deployment.
 *
 * Why this exists (not just "use openai provider"):
 *   Azure AI Foundry exposes ALL embedding deployments (OpenAI-built
 *   text-embedding-3-*, Cohere embed-v-4-*, third-party models) behind
 *   a unified `/openai/v1/` endpoint that speaks the OpenAI REST shape.
 *   In practice users who deploy a non-OpenAI model on Azure (e.g.
 *   `embed-v-4-0`) hit exactly that endpoint, with their own URL +
 *   deployment name + key, NOT OpenAI's hosted api.openai.com nor the
 *   Loxia-backend proxy.
 *
 *   The `openai` provider already speaks the OpenAI REST shape but
 *   defaults to api.openai.com and exposes a `url` override only via
 *   the constructor — not through the Settings UI. Rather than overload
 *   the openai provider with conditional UI ("are you on Azure or
 *   real OpenAI?"), it's clearer to expose a separate provider option
 *   labelled "Azure (custom OpenAI-compat deployment)" with its own
 *   form fields (endpoint, deployment, dimensions, key).
 *
 * Wire shape (matches the OpenAI embeddings REST API):
 *
 *   POST {endpoint}/embeddings
 *   Authorization: Bearer <api-key>
 *   Content-Type: application/json
 *
 *   {
 *     "input":           ["text 1", "text 2"],
 *     "model":           "embed-v-4-0",   // user's deployment name
 *     "encoding_format": "float"          // explicit; some compat layers
 *                                         // require it
 *   }
 *
 *   → { "data": [{ "embedding": [...] }, ...], "usage": {...} }
 *
 * Endpoint normalization: the user typically pastes the `baseURL` they
 * got from Azure (e.g. `.../openai/v1/`). We auto-append `embeddings`
 * so the user doesn't have to think about the suffix.
 *
 * Dimensions: model-dependent. The user supplies them — for
 * embed-v-4-0 the default is 1536; for OpenAI text-embedding-3-* it's
 * 1536/3072. We don't auto-detect because the base class needs to
 * validate dimensions on every response.
 */

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

const DEFAULT_DIMS = 1536;       // embed-v-4-0 / text-embedding-3-small default
const DEFAULT_MAX_BATCH = 16;    // safe across Azure deployments
const DEFAULT_MAX_TOKENS = 8191;

/**
 * Normalize the user-supplied endpoint into a full POST URL. The
 * Azure portal copy-paste typically gives the OpenAI-compat baseURL
 * (e.g. `.../openai/v1/`) — we append `embeddings` if needed, strip
 * trailing slashes, and leave already-suffixed URLs alone.
 *
 * Exported for tests + future shared use by other custom providers.
 *
 * @param {string} url
 * @returns {string}
 */
export function normalizeEmbeddingsUrl(url) {
  if (typeof url !== 'string' || !url.trim()) return url;
  let u = url.trim().replace(/\/+$/, '');
  if (!/\/embeddings$/.test(u)) u = `${u}/embeddings`;
  return u;
}

export class AzureCustomEmbeddingProvider extends EmbeddingProvider {
  /**
   * @param {object} opts
   * @param {string} opts.endpoint     - Raw or normalized URL; we auto-append `/embeddings`.
   * @param {string} opts.deployment   - The deployed model name (e.g. `embed-v-4-0`).
   * @param {() => string|null} opts.getApiKey - Returns the current API key.
   * @param {number} [opts.dimensions]   - Required for non-default models; defaults to 1536.
   * @param {number} [opts.maxBatchSize]
   * @param {number} [opts.timeoutMs]
   * @param {typeof fetch} [opts.fetchImpl]  - Injectable for tests.
   */
  constructor({
    endpoint,
    deployment,
    getApiKey,
    dimensions = DEFAULT_DIMS,
    maxBatchSize = DEFAULT_MAX_BATCH,
    timeoutMs,
    fetchImpl,
  } = {}) {
    super();
    if (!endpoint) throw new Error('AzureCustomEmbeddingProvider requires endpoint');
    if (!deployment) throw new Error('AzureCustomEmbeddingProvider requires deployment (the deployed model name)');
    if (typeof getApiKey !== 'function') {
      throw new Error('AzureCustomEmbeddingProvider requires a getApiKey() callback');
    }
    this._url = normalizeEmbeddingsUrl(endpoint);
    this._modelId = deployment;
    this._getApiKey = getApiKey;
    this._dimensions = Number.isInteger(dimensions) && dimensions > 0 ? dimensions : DEFAULT_DIMS;
    this._maxBatchSize = Number.isInteger(maxBatchSize) && maxBatchSize > 0 ? maxBatchSize : DEFAULT_MAX_BATCH;
    this._timeoutMs = timeoutMs;
    this._fetchImpl = fetchImpl;
  }

  getInfo() {
    return {
      name: 'azure-custom',
      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 API key set for the Azure custom deployment. Add one under Settings → Embeddings.' };
    return { ok: true };
  }

  async _embed(texts, _kind) {
    const apiKey = this._getApiKey();
    if (!apiKey) {
      throw new EmbeddingError(
        EMBEDDING_ERROR_CODES.AUTH_ERROR,
        'No API key set for the Azure custom deployment. Add one under Settings → Embeddings.',
        { provider: 'azure-custom', transient: false },
      );
    }
    return postEmbeddings({
      url: this._url,
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      // `encoding_format: 'float'` is explicit per Azure / Cohere Embed v4
      // best-practice docs. OpenAI's hosted endpoint accepts it without
      // change; the compat layer requires it for some non-OpenAI models.
      body: { input: texts, model: this._modelId, encoding_format: 'float' },
      providerName: 'azure-custom',
      extractVectors: extractOpenAIShape,
      timeoutMs: this._timeoutMs,
      fetchImpl: this._fetchImpl,
    });
  }

  getModelFingerprint() {
    return `azure-custom:${this._modelId}@${this._dimensions}d`;
  }
}
