#!/usr/bin/env node

const fs = require("node:fs");
const path = require("node:path");

const repoRoot = path.resolve(__dirname, "..", "..");
const registryPath = path.join(
  repoRoot,
  "packages/core/src/registries/model-provider-registry.generated.ts",
);
const modelsPath = path.join(
  repoRoot,
  "packages/core/src/registries/model-provider-types.generated.ts",
);
const docsRoot = path.join(repoRoot, "website/models-docs");
const providersDir = path.join(docsRoot, "providers");

const readFile = (filePath) => fs.readFileSync(filePath, "utf8");

const parseRegistry = (source) => {
  const marker = "MODEL_PROVIDER_REGISTRY";
  const start = source.indexOf(marker);
  if (start === -1) {
    throw new Error("MODEL_PROVIDER_REGISTRY not found.");
  }
  const braceStart = source.indexOf("{", start);
  const braceEnd = source.lastIndexOf("};");
  if (braceStart === -1 || braceEnd === -1) {
    throw new Error("Unable to parse provider registry object.");
  }
  const objectLiteral = source.slice(braceStart, braceEnd + 1);
  return new Function(`return ${objectLiteral};`)();
};

const parseProviderModels = (source) => {
  const providerRegex = /readonly\s+'([^']+)':\s+readonly\s+\[\n([\s\S]*?)\n\s*\];/g;
  const modelRegex = /'([^']+)'/g;
  const map = {};
  for (const match of source.matchAll(providerRegex)) {
    const providerId = match[1];
    const block = match[2];
    const models = [];
    for (const modelMatch of block.matchAll(modelRegex)) {
      models.push(modelMatch[1]);
    }
    map[providerId] = models;
  }
  return map;
};

const formatEnvList = (env) => {
  if (!env || env.length === 0) {
    return "None listed.";
  }
  return env.map((name) => `- \`${name}\``).join("\n");
};

const formatTableEnv = (env) => {
  if (!env || env.length === 0) {
    return "-";
  }
  return env.map((name) => `\`${name}\``).join("<br />");
};

const formatModelList = (models) => {
  if (!models || models.length === 0) {
    return "No models listed in the registry snapshot.";
  }
  const lines = models.map((model) => `- ${model}`).join("\n");
  return [
    "<details>",
    `  <summary>Show models (${models.length})</summary>`,
    "",
    lines,
    "",
    "</details>",
  ].join("\n");
};

const autoGeneratedNotice =
  "<!-- THIS FILE IS AUTO-GENERATED BY website/scripts/generate-model-docs.js. DO NOT EDIT MANUALLY. -->";

const buildOverview = (providerCount, modelCount) => `---
title: Models
slug: /
sidebar_position: 1
description: Explore ${providerCount}+ providers and ${modelCount}+ models supported by VoltAgent's model registry.
---

${autoGeneratedNotice}

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Models

Explore ${providerCount}+ providers and ${modelCount}+ models using VoltAgent's built-in model registry. Use \`provider/model\` strings for fast routing, or pass an ai-sdk \`LanguageModel\` when you need provider-specific control.

The registry is generated from [models.dev](https://models.dev) and bundled with VoltAgent. At runtime, VoltAgent checks required environment variables and reports the exact one that's missing.

## Highlights

- **Zero-import model strings** - Use \`provider/model\` IDs without adding provider packages.
- **Registry-backed env mapping** - VoltAgent knows which env vars each provider expects.
- **Type-aware model IDs** - \`ModelRouterModelId\` adds autocomplete and validation.
- **Runtime routing** - Pick models dynamically per request or tenant.
- **Bring your own LanguageModel** - Drop in ai-sdk providers for advanced options.

## Quick start (model strings)

<Tabs>
  <TabItem value="OpenAI" label="OpenAI">
    \`\`\`ts
    import { Agent } from "@voltagent/core";

    const agent = new Agent({
      name: "openai-summary",
      instructions: "Summarize the update in 2 bullets.",
      model: "openai/gpt-4.1-mini",
    });
    \`\`\`
  </TabItem>
  <TabItem value="Anthropic" label="Anthropic">
    \`\`\`ts
    import { Agent } from "@voltagent/core";

    const agent = new Agent({
      name: "claude-notes",
      instructions: "Turn notes into action items.",
      model: "anthropic/claude-3-5-haiku",
    });
    \`\`\`
  </TabItem>
  <TabItem value="Google" label="Google Gemini">
    \`\`\`ts
    import { Agent } from "@voltagent/core";

    const agent = new Agent({
      name: "gemini-translator",
      instructions: "Translate to Turkish and keep tone friendly.",
      model: "google/gemini-2.0-flash",
    });
    \`\`\`
  </TabItem>
  <TabItem value="xAI" label="xAI">
    \`\`\`ts
    import { Agent } from "@voltagent/core";

    const agent = new Agent({
      name: "grok-ideas",
      instructions: "Brainstorm five product names.",
      model: "xai/grok-3-mini",
    });
    \`\`\`
  </TabItem>
  <TabItem value="OpenRouter" label="OpenRouter">
    \`\`\`ts
    import { Agent } from "@voltagent/core";

    const agent = new Agent({
      name: "openrouter-agent",
      instructions: "Answer in short paragraphs.",
      model: "openrouter/anthropic/claude-3.5-haiku",
    });
    \`\`\`
  </TabItem>
</Tabs>

## Provider directory

Browse provider pages in the left navigation or visit the [Providers directory](/models-docs/providers/overview) for the full list.

For full model inventories per provider, see each provider page or explore [models.dev](https://models.dev).

## Type-safe model IDs

Use \`ModelRouterModelId\` to get IDE autocomplete for model strings:

\`\`\`ts
import type { ModelRouterModelId } from "@voltagent/core";

const modelId: ModelRouterModelId = "openai/gpt-4.1-mini";
\`\`\`

## Split workloads across models

Assign cheaper models to throughput-heavy steps and stronger models to critical analysis:

\`\`\`ts
import { Agent } from "@voltagent/core";

const ingestAgent = new Agent({
  name: "ingest-agent",
  instructions: "Extract entities and key facts from raw notes.",
  model: "google/gemini-2.0-flash",
});

const reviewAgent = new Agent({
  name: "review-agent",
  instructions: "Review the summary and flag risks or gaps.",
  model: "anthropic/claude-3-5-sonnet",
});
\`\`\`

## Runtime model selection

Pick a model based on request context:

\`\`\`ts
const agent = new Agent({
  name: "runtime-router",
  model: ({ context }) => {
    const tier = (context.get("tier") as string) || "fast";
    return tier === "fast"
      ? "openai/gpt-4.1-mini"
      : "anthropic/claude-3-5-sonnet";
  },
});
\`\`\`

## Provider options

Pass provider-specific options per request when you need them:

\`\`\`ts
const analyst = new Agent({
  name: "analyst",
  instructions: "Explain tradeoffs clearly and concisely.",
  model: "openai/o3-mini",
});

const response = await analyst.generateText("Compare JWTs vs cookies for auth.", {
  providerOptions: {
    openai: { reasoningEffort: "high" },
  },
});
\`\`\`

## Custom headers

If you need custom headers, pass an ai-sdk \`LanguageModel\` directly:

\`\`\`ts
import { Agent } from "@voltagent/core";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

const customProvider = createOpenAICompatible({
  name: "openai",
  baseURL: "https://api.openai.com/v1",
  apiKey: process.env.OPENAI_API_KEY,
  headers: {
    "X-Client-Source": "voltagent-docs",
  },
});

const agent = new Agent({
  name: "custom-agent",
  model: customProvider("gpt-4o-mini"),
});
\`\`\`

:::note
VoltAgent does not include fallback chains yet. Implement retries or failover in your app if needed.
:::

## Use ai-sdk directly

You can use ai-sdk provider modules anywhere VoltAgent expects a \`LanguageModel\`:

\`\`\`ts
import { mistral } from "@ai-sdk/mistral";
import { Agent } from "@voltagent/core";

const agent = new Agent({
  name: "mistral-agent",
  model: mistral("mistral-small-latest"),
});
\`\`\`
`;

const buildProvidersOverview = (providerIds, registry, _providerModels) => {
  const rows = providerIds
    .map((providerId) => {
      const entry = registry[providerId];
      const name = entry?.name ?? providerId;
      const env = formatTableEnv(entry?.env);
      const doc = entry?.doc ? `[Docs](${entry.doc})` : "-";
      return `| ${name} | \`${providerId}\` | ${env} | ${doc} |`;
    })
    .join("\n");

  return `---
title: Providers
description: Browse all providers supported by VoltAgent's model router.
sidebar_label: Overview
sidebar_position: 1
---

${autoGeneratedNotice}

# Providers

Each provider page includes:

- A usage example with \`provider/model\`
- Required environment variables
- Links to provider documentation
- Model list from the registry snapshot

## Provider directory

| Provider | Model prefix | Environment variables | Docs |
| --- | --- | --- | --- |
${rows}
`;
};

const preferredModelByProvider = {
  anthropic: "claude-3-5-sonnet",
  google: "gemini-2.0-flash",
  openai: "gpt-4o-mini",
  openrouter: "anthropic/claude-haiku-4-5",
  xai: "grok-4",
};

const buildProviderDoc = (providerId, entry, models) => {
  const name = entry?.name ?? providerId;
  const docLink = entry?.doc ? `- ${entry.doc}` : "- Not available.";
  const envBlock = formatEnvList(entry?.env);
  const baseUrlEnv = `${providerId.toUpperCase().replace(/-/g, "_")}_BASE_URL`;
  const baseUrl = entry?.api && entry.api.trim().length > 0 ? `\`${entry.api}\`` : "Not listed.";
  const usesOpenAICompatible = entry?.npm === "@ai-sdk/openai-compatible";
  const providerPackage = entry?.npm ? `\`${entry.npm}\`` : "Not listed.";
  const fallbackModel = models?.[0];
  const preferredModel = preferredModelByProvider[providerId];
  const exampleModel =
    preferredModel && models?.includes(preferredModel)
      ? preferredModel
      : fallbackModel || "model-id";
  const exampleModelId = `${providerId}/${exampleModel}`;

  return `---
title: ${name}
---

${autoGeneratedNotice}

# ${name}

Use \`${providerId}/<model>\` with VoltAgent's model router.

## Quick start

\`\`\`ts
import { Agent } from "@voltagent/core";

const agent = new Agent({
  name: "${providerId}-agent",
  instructions: "You are a helpful assistant",
  model: "${exampleModelId}",
});
\`\`\`

## Environment variables

${envBlock}

## Provider package

${providerPackage}

${usesOpenAICompatible ? "This provider uses the OpenAI-compatible adapter." : ""}

## Default base URL

${baseUrl}

You can override the base URL by setting \`${baseUrlEnv}\`.

## Provider docs

${docLink}

## Models

${formatModelList(models)}
`;
};

const main = () => {
  const registrySource = readFile(registryPath);
  const modelsSource = readFile(modelsPath);

  const registry = parseRegistry(registrySource);
  const providerModels = parseProviderModels(modelsSource);
  const providerIds = Object.keys(registry).sort((a, b) => a.localeCompare(b));
  const providerCount = providerIds.length;
  const modelCount = providerIds.reduce(
    (total, providerId) => total + (providerModels[providerId]?.length ?? 0),
    0,
  );

  fs.mkdirSync(providersDir, { recursive: true });
  fs.writeFileSync(
    path.join(docsRoot, "overview.md"),
    buildOverview(providerCount, modelCount),
    "utf8",
  );
  fs.writeFileSync(
    path.join(providersDir, "overview.md"),
    buildProvidersOverview(providerIds, registry, providerModels),
    "utf8",
  );

  for (const providerId of providerIds) {
    const entry = registry[providerId];
    const models = providerModels[providerId] ?? [];
    const content = buildProviderDoc(providerId, entry, models);
    fs.writeFileSync(path.join(providersDir, `${providerId}.md`), content, "utf8");
  }

  // eslint-disable-next-line no-console
  console.log(`Generated Models docs for ${providerCount} providers (${modelCount} models).`);
};

main();
