/**
 * Embedding credential preflight.
 *
 * v0.41.6.0 D1 — fail fast at sync/embed/import entry when the configured
 * embedding provider can't be reached. Without this, gbrain proceeds into
 * the import phase, hits 565 per-file embed errors, writes 565 identical
 * "OpenAI embedding requires OPENAI_API_KEY." rows to `~/.gbrain/sync-failures.jsonl`,
 * and blocks the sync bookmark from advancing.
 *
 * Routes through `gateway.diagnoseEmbedding()` so the structured reason
 * (missing_env / no_touchpoint / unknown_provider / etc.) drives a precise
 * user-facing error message.
 *
 * Skip protocol: callers SHOULD NOT call validateEmbeddingCreds when the
 * user explicitly passed `--no-embed` (the canonical opt-out). The
 * deferred-setup sentinel is owned by `assertEmbeddingEnabled` in
 * `embedding-dim-check.ts`; this preflight runs AFTER that check fires.
 */

import { diagnoseEmbedding, type EmbeddingDiagnosis } from './ai/gateway.ts';

/**
 * Tagged error thrown by validateEmbeddingCreds. CLI catch sites format
 * `.userMessage` to stderr and exit non-zero. The structured fields
 * (`provider`, `model`, `missingEnvVars`, `reason`) enable programmatic
 * consumers (`gbrain doctor --json`, future autopilot health checks) to
 * read state without parsing the human message.
 */
export class EmbeddingCredentialError extends Error {
  readonly diagnosis: EmbeddingDiagnosis;
  readonly userMessage: string;

  constructor(diagnosis: EmbeddingDiagnosis, userMessage: string) {
    super(userMessage);
    this.name = 'EmbeddingCredentialError';
    this.diagnosis = diagnosis;
    this.userMessage = userMessage;
  }
}

/**
 * Run the preflight. Throws EmbeddingCredentialError when the gateway
 * can't serve embeddings. Returns silently when ok.
 *
 * Pure function: reads nothing except what `gateway.diagnoseEmbedding()`
 * already had at gateway configure-time.
 */
export function validateEmbeddingCreds(): void {
  const d = diagnoseEmbedding();
  if (d.ok) return;
  throw new EmbeddingCredentialError(d, formatEmbeddingCredsError(d));
}

/**
 * Format a paste-ready, multi-line error message from a non-ok diagnosis.
 * Exported for tests and for the doctor JSON output.
 */
export function formatEmbeddingCredsError(d: EmbeddingDiagnosis): string {
  if (d.ok) return '';

  switch (d.reason) {
    case 'no_gateway_config':
      return [
        'Embedding gateway is not configured.',
        'This is usually a startup-order bug. Re-run with --no-embed to import',
        'without embedding, then file an issue at https://github.com/garrytan/gbrain/issues',
      ].join('\n');

    case 'no_model_configured':
      return [
        'No embedding model is configured for this brain.',
        '',
        '  Set one: gbrain config set embedding_model openai:text-embedding-3-small',
        '  Or skip embedding now: re-run with --no-embed',
      ].join('\n');

    case 'unknown_provider':
      return [
        `Embedding model "${d.model}" uses an unknown provider "${d.provider}".`,
        '',
        `  ${d.message}`,
        '',
        '  Pick a known provider: gbrain config set embedding_model openai:text-embedding-3-small',
      ].join('\n');

    case 'no_touchpoint':
      return [
        `Provider "${d.provider}" does not offer an embedding touchpoint.`,
        '',
        '  Switch providers: gbrain config set embedding_model openai:text-embedding-3-small',
        '  Or run with --no-embed to import-only and embed later.',
      ].join('\n');

    case 'user_provided_dims_unset':
      return [
        `Provider "${d.provider}" ships no default embedding dimension; set one explicitly.`,
        '',
        `  Re-init with the dimension: gbrain init --embedding-dimensions <N>`,
        '  (embedding_dimensions is a schema-sizing field — `config set` rejects it on purpose)',
        '  Or run with --no-embed to import-only and embed later.',
      ].join('\n');

    case 'missing_env': {
      const envs = d.missingEnvVars.join(', ');
      const primaryEnv = d.missingEnvVars[0];
      const lines = [
        `Embedding model "${d.model}" requires ${envs}.`,
        '',
        `Set it in your shell, or:`,
        `  • Re-run with --no-embed to import-only and embed later once the key is set.`,
      ];
      // Only offer a provider-switch hint when the current provider isn't openai
      // (otherwise we'd be suggesting they switch to the thing they already have).
      if (d.provider !== 'openai') {
        lines.push(`  • Switch providers: gbrain config set embedding_model openai:text-embedding-3-small`);
      } else {
        lines.push(`  • Switch providers: gbrain config set embedding_model voyage:voyage-3-large`);
      }
      lines.push('');
      lines.push(`Example shell setup:`);
      lines.push(`  export ${primaryEnv}=...`);
      return lines.join('\n');
    }
  }
}
