import type {
  EmbeddingModelV4,
  Experimental_BatchV4 as BatchV4,
  Experimental_DecisionModelV4 as DecisionModelV4,
  Experimental_ExtractionModelV4 as ExtractionModelV4,
  Experimental_VideoModelV4,
  FilesV4,
  ImageModelV4,
  LanguageModelV4,
  ProviderV4,
  Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
  Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
  SpeechModelV4,
  Experimental_SpeechTranslationModelV4 as SpeechTranslationModelV4,
  TranscriptionModelV4,
} from '@ai-sdk/provider';
import {
  resolveWith,
  type Resolvable,
  generateId,
  loadApiKey,
  withoutTrailingSlash,
  withUserAgentSuffix,
  type FetchFunction,
  type WebSocketConstructor,
} from '@ai-sdk/provider-utils';
import { Experimental_ExtractionLanguageModel as ExtractionLanguageModel } from '@ai-sdk/provider-utils/experimental-extraction';
import { DecisionGoogleModel } from './google-decision-model';
import type { GoogleDecisionModelId } from './google-decision-model-options';
import { VERSION } from './version';
import { GoogleEmbeddingModel } from './google-embedding-model';
import type { GoogleEmbeddingModelId } from './google-embedding-model-options';
import { GoogleBatch } from './google-batch';
import { GoogleLanguageModel } from './google-language-model';
import type { GoogleModelId } from './google-language-model-options';
import { googleTools } from './google-tools';

import type {
  GoogleImageSettings,
  GoogleImageModelId,
} from './google-image-settings';
import { GoogleImageModel } from './google-image-model';
import { GoogleFiles } from './google-files';
import { GoogleVideoModel } from './google-video-model';
import type { GoogleVideoModelId } from './google-video-settings';
import { GoogleSpeechModel } from './google-speech-model';
import type { GoogleSpeechModelId } from './google-speech-model-options';
import {
  GoogleInteractionsLanguageModel,
  type GoogleInteractionsModelInput,
} from './interactions/google-interactions-language-model';
import type { GoogleInteractionsModelId } from './interactions/google-interactions-language-model-options';
import type { GoogleInteractionsAgentName } from './interactions/google-interactions-agent';
import { GoogleRealtimeModel } from './realtime/google-realtime-model';
import { GoogleTranscriptionModel } from './transcription/google-transcription-model';
import type { GoogleTranscriptionModelId } from './transcription/google-transcription-model-options';
import { GoogleSpeechTranslationModel } from './speech-translation/google-speech-translation-model';
import type { GoogleSpeechTranslationModelId } from './speech-translation/google-speech-translation-model-options';

const DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
const googleFilesUrlPattern =
  /^https:\/\/generativelanguage\.googleapis\.com\/v1beta\/files\/.*$/;

export interface GoogleProvider extends ProviderV4 {
  (modelId: GoogleModelId): LanguageModelV4;

  languageModel(modelId: GoogleModelId): LanguageModelV4;

  chat(modelId: GoogleModelId): LanguageModelV4;

  /** Creates an experimental model using the Gemini Decisions API. */
  decisionModel(modelId: GoogleDecisionModelId): DecisionModelV4;
  /** @deprecated Use `decisionModel` instead. */
  evaluationModel(modelId: GoogleDecisionModelId): DecisionModelV4 & {
    doEvaluate: DecisionModelV4['doDecide'];
  };

  /** Creates an experimental typed field extraction model using Gemini. */
  extractionModel(modelId: GoogleModelId): ExtractionModelV4;

  experimental_batch(): BatchV4<{
    text: GoogleModelId;
    image: GoogleImageModelId;
  }>;

  /**
   * Creates a model for image generation.
   */
  image(
    modelId: GoogleImageModelId,
    settings?: GoogleImageSettings,
  ): ImageModelV4;

  /**
   * @deprecated Use `chat()` instead.
   */
  generativeAI(modelId: GoogleModelId): LanguageModelV4;

  /**
   * Creates a model for text embeddings.
   */
  embedding(modelId: GoogleEmbeddingModelId): EmbeddingModelV4;

  /**
   * Creates a model for text embeddings.
   */
  embeddingModel(modelId: GoogleEmbeddingModelId): EmbeddingModelV4;

  /**
   * @deprecated Use `embedding` instead.
   */
  textEmbedding(modelId: GoogleEmbeddingModelId): EmbeddingModelV4;

  /**
   * @deprecated Use `embeddingModel` instead.
   */
  textEmbeddingModel(modelId: GoogleEmbeddingModelId): EmbeddingModelV4;

  /**
   * Creates a model for video generation.
   */
  video(modelId: GoogleVideoModelId): Experimental_VideoModelV4;

  /**
   * Creates a model for video generation.
   */
  videoModel(modelId: GoogleVideoModelId): Experimental_VideoModelV4;

  /**
   * Creates an experimental model for streaming speech translation.
   */
  translation(
    modelId: GoogleSpeechTranslationModelId,
  ): SpeechTranslationModelV4;

  /**
   * Creates an experimental model for streaming speech translation.
   */
  speechTranslationModel(
    modelId: GoogleSpeechTranslationModelId,
  ): SpeechTranslationModelV4;

  /**
   * Creates a model for speech generation (text-to-speech).
   */
  speech(modelId: GoogleSpeechModelId): SpeechModelV4;

  /**
   * Creates a model for speech generation (text-to-speech).
   */
  speechModel(modelId: GoogleSpeechModelId): SpeechModelV4;

  /**
   * Creates a model for transcription (speech-to-text). Unary models
   * (e.g. `gemini-3.5-transcribe`) transcribe audio files; live models
   * (e.g. `gemini-3.5-transcribe-live`) stream transcription over the
   * Gemini Live API WebSocket via `experimental_streamTranscribe`.
   */
  transcription(modelId: GoogleTranscriptionModelId): TranscriptionModelV4;

  /**
   * Creates a model for transcription (speech-to-text).
   */
  transcriptionModel(modelId: GoogleTranscriptionModelId): TranscriptionModelV4;

  files(): FilesV4;

  /**
   * Creates a language model targeting the Gemini Interactions API
   * (`POST /v1beta/interactions`). Pass:
   *   - a model ID (string),
   *   - `{ agent: <name> }` to use a known Gemini agent preset, or
   *   - `{ managedAgent: <name> }` to use a user-defined agent created via
   *     the `/v1beta/agents` endpoint.
   */
  interactions(
    modelIdOrAgent:
      | GoogleInteractionsModelId
      | { agent: GoogleInteractionsAgentName }
      | { managedAgent: string },
  ): LanguageModelV4;

  experimental_realtime: RealtimeFactoryV4;

  tools: typeof googleTools;
}

export interface GoogleProviderSettings {
  /** Settings for downloading JSON Lines batch results. */
  batchResultDownloads?: {
    /** Maximum UTF-8 bytes per row, excluding LF. Defaults to 64 MiB. */
    maxLineBytes?: number;
  };

  /**
   * Use a different URL prefix for API calls, e.g. to use proxy servers.
   * The default prefix is `https://generativelanguage.googleapis.com/v1beta`.
   */
  baseURL?: string;

  /**
   * API key that is being send using the `x-goog-api-key` header.
   * It defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.
   */
  apiKey?: Resolvable<string>;

  /**
   * Custom headers to include in the requests.
   */
  headers?: Record<string, string | undefined>;

  /**
   * Custom fetch implementation. You can use it as a middleware to intercept requests,
   * or to provide a custom fetch implementation for e.g. testing.
   */
  fetch?: FetchFunction;

  /**
   * Optional function to generate a unique ID for each request.
   */
  generateId?: () => string;

  /**
   * Custom WebSocket implementation. This is useful for testing or for
   * runtimes that need a WebSocket constructor with header support.
   */
  webSocket?: WebSocketConstructor;

  /**
   * Custom provider name
   * Defaults to 'google.generative-ai'.
   */
  name?: string;
}

const supportedExternalUrlMediaTypes = [
  'text/html',
  'text/css',
  'text/plain',
  'text/xml',
  'text/csv',
  'text/rtf',
  'text/javascript',
  'application/json',
  'application/pdf',
  'image/bmp',
  'image/jpeg',
  'image/png',
  'image/webp',
  'video/mp4',
  'video/mpeg',
  'video/quicktime',
  'video/avi',
  'video/x-flv',
  'video/mpg',
  'video/webm',
  'video/wmv',
  'video/3gpp',
];

const externalHttpsUrlPattern = /^https:\/\/.*$/;

function supportsExternalFileUrls(modelId: string) {
  return /(^|\/)gemini-/.test(modelId) && !/(^|\/)gemini-2\.0/.test(modelId);
}

/**
 * Create a Google provider instance.
 */
export function createGoogle(
  options: GoogleProviderSettings = {},
): GoogleProvider {
  const baseURL = withoutTrailingSlash(options.baseURL) ?? DEFAULT_BASE_URL;

  const providerName = options.name ?? 'google.generative-ai';

  const getHeaders = () =>
    resolveWith(options.apiKey, apiKey =>
      withUserAgentSuffix(
        {
          'x-goog-api-key': loadApiKey({
            apiKey,
            environmentVariableName: 'GOOGLE_GENERATIVE_AI_API_KEY',
            description: 'Google Generative AI',
          }),
          ...options.headers,
        },
        `ai-sdk-google/${VERSION}`,
      ),
    );

  const getSupportedUrls = (
    modelId?: GoogleModelId,
    includeExternalUrls = modelId == null || supportsExternalFileUrls(modelId),
  ) => ({
    '*': [
      googleFilesUrlPattern,
      new RegExp(`^${baseURL}/files/.*$`),
      new RegExp(
        `^https://(?:www\\.)?youtube\\.com/watch\\?v=[\\w-]+(?:&[\\w=&.-]*)?$`,
      ),
      new RegExp(`^https://youtu\\.be/[\\w-]+(?:\\?[\\w=&.-]*)?$`),
    ],
    ...(includeExternalUrls
      ? Object.fromEntries(
          supportedExternalUrlMediaTypes.map(mediaType => [
            mediaType,
            [externalHttpsUrlPattern],
          ]),
        )
      : {}),
  });

  const languageModelConfig = {
    provider: providerName,
    baseURL,
    headers: getHeaders,
    generateId: options.generateId ?? generateId,
    fetch: options.fetch,
  };

  const createChatModel = (modelId: GoogleModelId) =>
    new GoogleLanguageModel(modelId, {
      ...languageModelConfig,
      supportedUrls: () => getSupportedUrls(modelId),
    });

  const createBatch = () =>
    new GoogleBatch({
      provider: `${providerName.replace(/\.generative-ai$/, '')}.batch`,
      maxLineBytes: options.batchResultDownloads?.maxLineBytes,
      config: languageModelConfig,
      // Batch prompt conversion happens before the model is available to the
      // provider. Only advertise URL support shared by every batch model.
      supportedUrls: getSupportedUrls(undefined, false),
    });

  const createEmbeddingModel = (modelId: GoogleEmbeddingModelId) =>
    new GoogleEmbeddingModel(modelId, {
      provider: providerName,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });

  const createImageModel = (
    modelId: GoogleImageModelId,
    settings: GoogleImageSettings = {},
  ) =>
    new GoogleImageModel(modelId, settings, {
      provider: providerName,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });

  const createFiles = () =>
    new GoogleFiles({
      provider: providerName,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });

  const createVideoModel = (modelId: GoogleVideoModelId) =>
    new GoogleVideoModel(modelId, {
      provider: providerName,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
      generateId: options.generateId ?? generateId,
    });

  const createRealtimeModel = (modelId: string) =>
    new GoogleRealtimeModel(modelId, {
      provider: `${providerName}.realtime`,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });

  const createSpeechTranslationModel = (
    modelId: GoogleSpeechTranslationModelId,
  ) =>
    new GoogleSpeechTranslationModel(modelId, {
      provider: `${providerName}.speech-translation`,
      baseURL,
      headers: getHeaders,
      webSocket: options.webSocket,
    });

  const createSpeechModel = (modelId: GoogleSpeechModelId) =>
    new GoogleSpeechModel(modelId, {
      provider: `${providerName}.speech`,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });

  const createTranscriptionModel = (modelId: GoogleTranscriptionModelId) =>
    new GoogleTranscriptionModel(modelId, {
      provider: `${providerName}.transcription`,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
      webSocket: options.webSocket,
    });

  const experimentalRealtimeFactory = Object.assign(
    (modelId: string) => createRealtimeModel(modelId),
    {
      getToken: async (tokenOptions: RealtimeFactoryV4GetTokenOptions) => {
        const model = createRealtimeModel(tokenOptions.model);
        const secret = await model.doCreateClientSecret({
          sessionConfig: tokenOptions.sessionConfig,
          expiresAfterSeconds: tokenOptions.expiresAfterSeconds,
        });

        return {
          token: secret.token,
          url: secret.url,
          expiresAt: secret.expiresAt,
        };
      },
    },
  ) as RealtimeFactoryV4;

  const createInteractionsModel = (
    modelIdOrAgent:
      | GoogleInteractionsModelId
      | { agent: GoogleInteractionsAgentName }
      | { managedAgent: string },
  ) =>
    new GoogleInteractionsLanguageModel(
      modelIdOrAgent as GoogleInteractionsModelInput,
      {
        provider: `${providerName}.interactions`,
        baseURL,
        headers: getHeaders,
        generateId: options.generateId ?? generateId,
        fetch: options.fetch,
      },
    );

  const provider = function (modelId: GoogleModelId) {
    if (new.target) {
      throw new Error(
        'The Google Generative AI model function cannot be called with the new keyword.',
      );
    }

    return createChatModel(modelId);
  };

  provider.specificationVersion = 'v4' as const;
  provider.languageModel = createChatModel;
  provider.chat = createChatModel;
  provider.generativeAI = createChatModel;
  provider.decisionModel = (modelId: GoogleDecisionModelId) =>
    new DecisionGoogleModel(modelId, {
      provider: `${providerName.replace(/\.generative-ai$/, '')}.decision`,
      baseURL,
      headers: getHeaders,
      fetch: options.fetch,
    });
  provider.evaluationModel =
    provider.decisionModel as GoogleProvider['evaluationModel'];
  provider.extractionModel = (modelId: GoogleModelId) =>
    new ExtractionLanguageModel({
      model: createChatModel(modelId),
      provider: `${providerName.replace(/\.generative-ai$/, '')}.extraction`,
    });
  provider.experimental_batch = createBatch;
  provider.embedding = createEmbeddingModel;
  provider.embeddingModel = createEmbeddingModel;
  provider.textEmbedding = createEmbeddingModel;
  provider.textEmbeddingModel = createEmbeddingModel;
  provider.image = createImageModel;
  provider.imageModel = createImageModel;
  provider.video = createVideoModel;
  provider.videoModel = createVideoModel;
  provider.experimental_realtime = experimentalRealtimeFactory;
  provider.files = createFiles;
  provider.speech = createSpeechModel;
  provider.speechModel = createSpeechModel;
  provider.transcription = createTranscriptionModel;
  provider.transcriptionModel = createTranscriptionModel;
  provider.translation = createSpeechTranslationModel;
  provider.speechTranslationModel = createSpeechTranslationModel;
  provider.interactions = createInteractionsModel;
  provider.tools = googleTools;

  return provider as GoogleProvider;
}

/**
 * Default Google Generative AI provider instance.
 */
export const google = createGoogle();
