<?php

declare(strict_types=1);

namespace KimaAI\Ai;

defined('ABSPATH') || exit;

final class AiModels {
  private const UNIT_PER_1M_TOKENS = 1 / 1000000;
  private const VERBOSITY_0 = ['low', 'medium'];
  private const VERBOSITY = ['low', 'medium', 'high'];
  private const REASONING_O = ['low', 'medium', 'high'];
  private const REASONING_GPT5_2 = ['none', 'low', 'medium', 'high', 'xhigh'];
  private const REASONING_GPT5_2_PRO = ['medium', 'high', 'xhigh'];
  private const REASONING_GPT5 = ['none', 'minimal', 'low', 'medium', 'high'];
  private const TOOLS_FULL = ['web_search', 'image_generation', 'file_search', 'code_interpreter'];
  private const TOOLS_LITE = ['web_search', 'file_search', 'code_interpreter'];
  private const TAGS_GPT_CORE_FULL = ['core', 'chat', 'vision', 'files', 'functions', 'json', 'responses', 'mcp', 'reasoning', 'verbosity'];
  private const TAGS_GPT_CHAT_LITE = ['core', 'chat', 'vision', 'files', 'responses', 'mcp'];
  private const RES_GPT_IMAGE = [
    [
      'name' => '1024x1024',
      'label' => '1024x1024',
    ],
    [
      'name' => '1024x1536',
      'label' => '1024x1536',
    ],
    [
      'name' => '1536x1024',
      'label' => '1536x1024',
    ],
  ];

  /**
   * Return OpenAI model metadata definitions.
   */
  public static function openAiModels(): array {
    // Base “templates” for common model families/variants.
    $base_token_completion = [
      'features' => ['completion'],
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
    ];

    $base_gpt5_full = self::merge($base_token_completion, [
      'family' => 'gpt-5',
      'tags' => self::TAGS_GPT_CORE_FULL,
      'tools' => self::TOOLS_FULL,
      'maxCompletionTokens' => 128000,
      'maxContextualTokens' => 400000,
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    $base_gpt5_chat_lite = self::merge($base_token_completion, [
      'family' => 'gpt-5',
      'tags' => self::TAGS_GPT_CHAT_LITE,
      'tools' => self::TOOLS_LITE,
    ]);

    $base_gpt4 = self::merge($base_token_completion, [
      'family' => 'gpt-4',
      'tags' => ['core', 'chat', 'vision', 'functions', 'json', 'finetune', 'responses', 'mcp'],
      'tools' => ['web_search', 'image_generation', 'code_interpreter'],
    ]);

    // Use a map keyed by model id to naturally de-duplicate and to simplify lookups/overrides.
    $models = [];

    // --- GPT-5.2 family ---
    $models['gpt-5.2'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5.2',
      'name' => 'GPT-5.2',
      'price' => [
        'in' => 1.75,
        'out' => 14.00,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5_2,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    $models['gpt-5.2-chat-latest'] = self::merge($base_gpt5_chat_lite, [
      'model' => 'gpt-5.2-chat-latest',
      'name' => 'GPT-5.2 Chat',
      'price' => [
        'in' => 1.75,
        'out' => 14.00,
      ],
      'maxCompletionTokens' => 128000,
      'maxContextualTokens' => 400000,
    ]);

    $models['gpt-5.2-pro'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5.2-pro',
      'name' => 'GPT-5.2 Pro',
      'price' => [
        'in' => 21.00,
        'out' => 168.00,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5_2_PRO,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    // --- GPT-5.1 / GPT-5 / mini / nano ---
    $models['gpt-5.1'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5.1',
      'name' => 'GPT-5.1',
      'price' => [
        'in' => 1.25,
        'out' => 10.00,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    $models['gpt-5'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5',
      'name' => 'GPT-5',
      'price' => [
        'in' => 1.25,
        'out' => 10.00,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    $models['gpt-5-mini'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5-mini',
      'name' => 'GPT-5 Mini',
      'price' => [
        'in' => 0.25,
        'out' => 2.00,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    $models['gpt-5-nano'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5-nano',
      'name' => 'GPT-5 Nano',
      'price' => [
        'in' => 0.05,
        'out' => 0.40,
      ],
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    // GPT-5 chat-latest has different context limits and params
    $models['gpt-5-chat-latest'] = self::merge($base_gpt5_chat_lite, [
      'model' => 'gpt-5-chat-latest',
      'name' => 'GPT-5 Chat',
      'price' => [
        'in' => 1.25,
        'out' => 10.00,
      ],
      'maxCompletionTokens' => 16384,
      'maxContextualTokens' => 128000,
      'params' => [
        'verbosity' => self::VERBOSITY_0,
      ],
    ]);

    $models['gpt-5-pro'] = self::merge($base_gpt5_full, [
      'model' => 'gpt-5-pro',
      'name' => 'GPT-5 Pro',
      'price' => [
        'in' => 15.00,
        'out' => 120.00,
      ],
      'maxCompletionTokens' => 272000,
      'maxContextualTokens' => 400000,
      'params' => [
        'reasoning' => self::REASONING_GPT5,
        'verbosity' => self::VERBOSITY,
      ],
    ]);

    // --- GPT-4.x ---
    $models['gpt-4.1'] = self::merge($base_gpt4, [
      'model' => 'gpt-4.1',
      'name' => 'GPT-4.1',
      'price' => [
        'in' => 2.00,
        'out' => 8.00,
      ],
      'maxCompletionTokens' => 32768,
      'maxContextualTokens' => 1047576,
    ]);

    $models['gpt-4.1-mini'] = self::merge($base_gpt4, [
      'model' => 'gpt-4.1-mini',
      'name' => 'GPT-4.1 Mini',
      'price' => [
        'in' => 0.40,
        'out' => 1.60,
      ],
      'maxCompletionTokens' => 32768,
      'maxContextualTokens' => 1047576,
    ]);

    $models['gpt-4.1-nano'] = self::merge($base_gpt4, [
      'model' => 'gpt-4.1-nano',
      'name' => 'GPT-4.1 Nano',
      'price' => [
        'in' => 0.10,
        'out' => 0.40,
      ],
      'maxCompletionTokens' => 32768,
      'maxContextualTokens' => 1047576,
      // original had only image_generation
      'tools' => ['image_generation'],
    ]);

    $models['gpt-4o'] = self::merge($base_gpt4, [
      'model' => 'gpt-4o',
      'name' => 'GPT-4o',
      'price' => [
        'in' => 2.50,
        'out' => 10.00,
      ],
      'maxCompletionTokens' => 16384,
      'maxContextualTokens' => 128000,
      'tags' => ['core', 'chat', 'vision', 'functions', 'json', 'finetune', 'mcp', 'responses'],
    ]);

    $models['gpt-4o-mini'] = self::merge($base_gpt4, [
      'model' => 'gpt-4o-mini',
      'name' => 'GPT-4o Mini',
      'price' => [
        'in' => 0.15,
        'out' => 0.60,
      ],
      'maxCompletionTokens' => 16384,
      'maxContextualTokens' => 128000,
      'finetune' => [
        'in' => 0.15,
        'out' => 0.60,
        'train' => 3.00,
      ],
      'tags' => ['core', 'chat', 'vision', 'functions', 'json', 'finetune', 'mcp', 'responses'],
    ]);

    // --- o1 / o3 ---
    $models['o1'] = self::merge($base_token_completion, [
      'model' => 'o1',
      'name' => 'o1',
      'family' => 'o1',
      'price' => [
        'in' => 15.00,
        'out' => 60.00,
      ],
      'maxCompletionTokens' => 100000,
      'maxContextualTokens' => 200000,
      'tags' => ['core', 'chat', 'o1-model', 'reasoning', 'mcp'],
      'params' => [
        'reasoning' => self::REASONING_O,
      ],
    ]);

    $models['o1-mini'] = self::merge($base_token_completion, [
      'model' => 'o1-mini',
      'name' => 'o1 Mini',
      'family' => 'o1',
      'price' => [
        'in' => 1.10,
        'out' => 4.40,
      ],
      'maxCompletionTokens' => 65536,
      'maxContextualTokens' => 128000,
      'tags' => ['core', 'chat', 'o1-model', 'reasoning', 'mcp'],
      'params' => [
        'reasoning' => self::REASONING_O,
      ],
    ]);

    $models['o3'] = self::merge($base_token_completion, [
      'model' => 'o3',
      'name' => 'o3',
      'family' => 'o3',
      'price' => [
        'in' => 15.00,
        'out' => 60.00,
      ],
      'maxCompletionTokens' => 100000,
      'maxContextualTokens' => 200000,
      'tags' => ['core', 'chat', 'o1-model', 'reasoning', 'responses', 'mcp'],
      'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      'params' => [
        'reasoning' => self::REASONING_O,
      ],
    ]);

    $models['o3-mini'] = self::merge($base_token_completion, [
      'model' => 'o3-mini',
      'name' => 'o3 Mini',
      'family' => 'o3',
      'price' => [
        'in' => 1.10,
        'out' => 4.40,
      ],
      'maxCompletionTokens' => 100000,
      'maxContextualTokens' => 200000,
      'tags' => ['core', 'chat', 'o1-model', 'reasoning', 'responses', 'mcp'],
      'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      'params' => [
        'reasoning' => self::REASONING_O,
      ],
    ]);

    // --- Realtime ---
    $models['gpt-realtime'] = [
      'model' => 'gpt-realtime',
      'name' => 'GPT Realtime',
      'family' => 'realtime',
      'features' => ['core', 'realtime', 'functions'],
      'price' => [
        'text' => [
          'in' => 4.00,
          'cache' => 0.40,
          'out' => 16.00,
        ],
        'audio' => [
          'in' => 32.00,
          'cache' => 0.40,
          'out' => 64.00,
        ],
        'image' => [
          'in' => 5.00,
          'cache' => 0.50,
        ],
      ],
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 128000,
      'finetune' => false,
      'tags' => ['core', 'realtime', 'functions', 'vision', 'mcp'],
    ];

    $models['gpt-realtime-mini'] = [
      'model' => 'gpt-realtime-mini',
      'name' => 'GPT Realtime Mini',
      'family' => 'realtime',
      'features' => ['core', 'realtime', 'functions'],
      'price' => [
        'text' => [
          'in' => 0.60,
          'cache' => 0.06,
          'out' => 2.40,
        ],
        'audio' => [
          'in' => 10.00,
          'cache' => 0.06,
          'out' => 20.00,
        ],
      ],
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 32000,
      'finetune' => false,
      'tags' => ['core', 'realtime', 'functions', 'vision'],
    ];

    $models['gpt-4o-realtime-preview'] = [
      'model' => 'gpt-4o-realtime-preview',
      'name' => 'GPT-4o Realtime (Preview)',
      'family' => 'realtime',
      'features' => ['core', 'realtime', 'functions'],
      'price' => [
        'text' => [
          'in' => 5.00,
          'cache' => 2.50,
          'out' => 20.00,
        ],
        'audio' => [
          'in' => 40.00,
          'cache' => 2.50,
          'out' => 80.00,
        ],
      ],
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 128000,
      'finetune' => false,
      'tags' => ['core', 'realtime', 'functions', 'vision'],
    ];

    $models['gpt-4o-mini-realtime-preview'] = [
      'model' => 'gpt-4o-mini-realtime-preview',
      'name' => 'GPT-4o Mini Realtime (Preview)',
      'family' => 'realtime',
      'features' => ['core', 'realtime', 'functions'],
      'price' => [
        'text' => [
          'in' => 0.60,
          'cache' => 0.30,
          'out' => 2.40,
        ],
        'audio' => [
          'in' => 10.00,
          'cache' => 0.30,
          'out' => 20.00,
        ],
      ],
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 128000,
      'finetune' => false,
      'tags' => ['core', 'realtime', 'functions', 'vision'],
    ];

    // --- Legacy GPT-4 / 3.5 ---
    $models['gpt-4'] = self::merge($base_token_completion, [
      'model' => 'gpt-4',
      'name' => 'GPT-4',
      'family' => 'gpt-4',
      'price' => [
        'in' => 30.00,
        'out' => 60.00,
      ],
      'maxCompletionTokens' => 8192,
      'maxContextualTokens' => 8192,
      'tags' => ['core', 'chat', 'functions'],
    ]);

    $models['gpt-4-turbo'] = self::merge($base_token_completion, [
      'model' => 'gpt-4-turbo',
      'name' => 'GPT-4 Turbo',
      'family' => 'gpt-4',
      'price' => [
        'in' => 10.00,
        'out' => 30.00,
      ],
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 128000,
      'tags' => ['core', 'chat', 'vision', 'functions', 'json'],
    ]);

    $models['gpt-3.5-turbo'] = self::merge($base_token_completion, [
      'model' => 'gpt-3.5-turbo',
      'name' => 'GPT-3.5 Turbo',
      'family' => 'gpt-3',
      'price' => [
        'in' => 0.50,
        'out' => 1.50,
      ],
      'maxCompletionTokens' => 4096,
      'maxContextualTokens' => 16385,
      'finetune' => [
        'in' => 3.00,
        'out' => 6.00,
        'train' => 8.00,
      ],
      'tags' => ['core', 'chat', '4k', 'finetune', 'functions'],
    ]);

    // --- Image ---
    $models['gpt-image-1.5'] = [
      'model' => 'gpt-image-1.5',
      'name' => 'GPT Image 1.5',
      'family' => 'gpt-image',
      'features' => ['text-to-image'],
      'resolutions' => self::RES_GPT_IMAGE,
      'type' => 'token',
      'mode' => 'image',
      'price' => [
        'in' => 8.00,
        'out' => 32.00,
        'cached' => 2.00,
      ],
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'tags' => ['core', 'image', 'image-edit', 'responses'],
    ];

    $models['gpt-image-1'] = [
      'model' => 'gpt-image-1',
      'name' => 'GPT Image 1',
      'family' => 'gpt-image',
      'features' => ['text-to-image'],
      'resolutions' => self::RES_GPT_IMAGE,
      'type' => 'token',
      'mode' => 'image',
      'price' => [
        'in' => 10.00,
        'out' => 40.00,
        'cached' => 2.50,
      ],
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'tags' => ['core', 'image', 'image-edit', 'responses'],
    ];

    $models['gpt-image-1-mini'] = [
      'model' => 'gpt-image-1-mini',
      'name' => 'GPT Image 1 Mini',
      'family' => 'gpt-image',
      'features' => ['text-to-image'],
      'resolutions' => self::RES_GPT_IMAGE,
      'type' => 'token',
      'mode' => 'image',
      'price' => [
        'in' => 2.50,
        'out' => 8.00,
        'cached' => 0.25,
      ],
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'tags' => ['core', 'image', 'image-edit', 'responses'],
    ];

    $models['dall-e-3'] = [
      'model' => 'dall-e-3',
      'name' => 'DALL-E 3',
      'family' => 'dall-e',
      'features' => ['text-to-image'],
      'resolutions' => [
        [
          'name' => '1024x1024',
          'label' => '1024x1024',
          'price' => 0.040,
        ],
        [
          'name' => '1024x1792',
          'label' => '1024x1792',
          'price' => 0.080,
        ],
        [
          'name' => '1792x1024',
          'label' => '1792x1024',
          'price' => 0.080,
        ],
      ],
      'type' => 'image',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'image'],
    ];

    $models['dall-e-3-hd'] = [
      'model' => 'dall-e-3-hd',
      'name' => 'DALL-E 3 (HD)',
      'family' => 'dall-e',
      'features' => ['text-to-image'],
      'resolutions' => [
        [
          'name' => '1024x1024',
          'label' => '1024x1024',
          'price' => 0.080,
        ],
        [
          'name' => '1024x1792',
          'label' => '1024x1792',
          'price' => 0.120,
        ],
        [
          'name' => '1792x1024',
          'label' => '1792x1024',
          'price' => 0.120,
        ],
      ],
      'type' => 'image',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'image'],
    ];

    // --- Video (Sora) ---
    $models['sora-2'] = [
      'model' => 'sora-2',
      'name' => 'Sora 2',
      'family' => 'sora',
      'features' => ['text-to-video'],
      'resolutions' => [
        [
          'name' => '720x1280',
          'label' => 'Portrait (720x1280)',
          'price' => 0.10,
        ],
        [
          'name' => '1280x720',
          'label' => 'Landscape (1280x720)',
          'price' => 0.10,
        ],
      ],
      'durations' => [4, 8, 12],
      'type' => 'video',
      'unit' => 'second',
      'finetune' => false,
      'tags' => ['core', 'video'],
    ];

    $models['sora-2-pro'] = [
      'model' => 'sora-2-pro',
      'name' => 'Sora 2 Pro',
      'family' => 'sora',
      'features' => ['text-to-video'],
      'resolutions' => [
        [
          'name' => '720x1280',
          'label' => 'Portrait (720x1280)',
          'price' => 0.30,
        ],
        [
          'name' => '1280x720',
          'label' => 'Landscape (1280x720)',
          'price' => 0.30,
        ],
        [
          'name' => '1024x1792',
          'label' => 'Portrait High (1024x1792)',
          'price' => 0.50,
        ],
        [
          'name' => '1792x1024',
          'label' => 'Landscape High (1792x1024)',
          'price' => 0.50,
        ],
      ],
      'durations' => [4, 8, 12],
      'type' => 'video',
      'unit' => 'second',
      'finetune' => false,
      'tags' => ['core', 'video'],
    ];

    // --- Embeddings ---
    $models['text-embedding-3-small'] = [
      'model' => 'text-embedding-3-small',
      'name' => 'Embedding 3-Small',
      'family' => 'text-embedding',
      'features' => ['embedding'],
      'price' => 0.02,
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'dimensions' => [512, 1536],
      'tags' => ['core', 'embedding'],
    ];

    $models['text-embedding-3-large'] = [
      'model' => 'text-embedding-3-large',
      'name' => 'Embedding 3-Large',
      'family' => 'text-embedding',
      'features' => ['embedding'],
      'price' => 0.13,
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'dimensions' => [256, 1024, 3072],
      'tags' => ['core', 'embedding'],
    ];

    $models['text-embedding-ada-002'] = [
      'model' => 'text-embedding-ada-002',
      'name' => 'Embedding Ada-002',
      'family' => 'text-embedding',
      'features' => ['embedding'],
      'price' => 0.10,
      'type' => 'token',
      'unit' => self::UNIT_PER_1M_TOKENS,
      'finetune' => false,
      'dimensions' => [1536],
      'tags' => ['core', 'embedding'],
    ];

    // --- Audio (Whisper / Transcribe) ---
    $models['gpt-4o-transcribe'] = [
      'model' => 'gpt-4o-transcribe',
      'name' => 'GPT-4o Transcribe',
      'family' => 'whisper',
      'features' => ['speech-to-text'],
      'price' => 0.006,
      'type' => 'second',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'audio'],
    ];

    $models['gpt-4o-mini-transcribe'] = [
      'model' => 'gpt-4o-mini-transcribe',
      'name' => 'GPT-4o Mini Transcribe',
      'family' => 'whisper',
      'features' => ['speech-to-text'],
      'price' => 0.003,
      'type' => 'second',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'audio'],
    ];

    $models['whisper-1'] = [
      'model' => 'whisper-1',
      'name' => 'Whisper',
      'family' => 'whisper',
      'features' => ['speech-to-text'],
      'price' => 0.006,
      'type' => 'second',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'audio'],
    ];

    // --- Deprecated ---
    $models['gpt-4.5-preview'] = self::merge($base_token_completion, [
      'model' => 'gpt-4.5-preview',
      'name' => 'GPT-4.5 (Preview)',
      'family' => 'gpt4.5',
      'price' => [
        'in' => 75.00,
        'out' => 150.00,
      ],
      'maxCompletionTokens' => 16384,
      'maxContextualTokens' => 128000,
      'tags' => ['core', 'chat', 'vision', 'functions', 'json', 'deprecated'],
    ]);

    $models['dall-e'] = [
      'model' => 'dall-e',
      'name' => 'DALL-E 2',
      'family' => 'dall-e',
      'features' => ['text-to-image'],
      'resolutions' => [
        [
          'name' => '256x256',
          'label' => '256x256',
          'price' => 0.016,
        ],
        [
          'name' => '512x512',
          'label' => '512x512',
          'price' => 0.018,
        ],
        [
          'name' => '1024x1024',
          'label' => '1024x1024',
          'price' => 0.020,
        ],
      ],
      'type' => 'image',
      'unit' => 1,
      'finetune' => false,
      'tags' => ['core', 'image', 'deprecated'],
    ];

    return array_values($models);
  }

  /**
   * Replicate model registry (completion + embedding).
   *
   * Keep the shape consistent with openAiModels() so the UI/filtering code can be shared:
   * - model, name, family, features, tags, (optional) price/type/unit/etc.
   */
  public static function replicateModels(): array {
    $models = [
      // ---------------------------------------------------------------------
      // Completion / chat models.
      // ---------------------------------------------------------------------
      [
        'model' => 'meta/meta-llama-3-70b-instruct',
        'name' => 'Meta Llama 3 70B Instruct',
        'family' => 'llama3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'meta/meta-llama-3-70b',
        'name' => 'Meta Llama 3 70B',
        'family' => 'llama3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'meta/meta-llama-3-8b-instruct',
        'name' => 'Meta Llama 3 8B Instruct',
        'family' => 'llama3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'meta/meta-llama-3-8b',
        'name' => 'Meta Llama 3 8B',
        'family' => 'llama3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'anthropic/claude-3.5-sonnet',
        'name' => 'Anthropic Claude 3.5 Sonnet',
        'family' => 'claude3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'anthropic/claude-3.5-haiku',
        'name' => 'Anthropic Claude 3.5 Haiku',
        'family' => 'claude3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],
      [
        'model' => 'anthropic/claude-3.7-sonnet',
        'name' => 'Anthropic Claude 3.7 Sonnet',
        'family' => 'claude3',
        'features' => ['completion'],
        'tags' => ['core', 'chat'],
      ],

      // ---------------------------------------------------------------------
      // Embedding models.
      // ---------------------------------------------------------------------
      [
        'model' => 'nateraw/bge-large-en-v1.5',
        'name' => 'BGE Large EN v1.5',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding'],
      ],
      [
        'model' => 'lucataco/nomic-embed-text-v1',
        'name' => 'Nomic Embed Text v1',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding'],
      ],
      [
        'model' => 'saattrupdan/multilingual-e5-large-instruct',
        'name' => 'Multilingual E5 Large (Instruct)',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding'],
      ],
      [
        'model' => 'ibm-granite/granite-embedding-278m-multilingual',
        'name' => 'IBM Granite Embedding 278M (Multilingual)',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding'],
      ],
    ];

    return $models;
  }

  /**
   * Curated OpenRouter model registry.
   *
   * These IDs use OpenRouter's provider/model format.
   */
  public static function openRouterModels(): array {
    return [
      [
        'model' => 'anthropic/claude-opus-4.6',
        'name' => 'Anthropic Claude Opus 4.6',
        'family' => 'claude',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'anthropic/claude-sonnet-4.6',
        'name' => 'Anthropic Claude Sonnet 4.6',
        'family' => 'claude',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'deepseek/deepseek-v3.2',
        'name' => 'DeepSeek V3.2',
        'family' => 'deepseek',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'google/gemini-3-flash-preview',
        'name' => 'Google Gemini 3 Flash Preview',
        'family' => 'gemini',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'minimax/minimax-m2.5',
        'name' => 'MiniMax M2.5',
        'family' => 'minimax',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'pro', 'openrouter'],
      ],
      [
        'model' => 'minimax/minimax-m2.7',
        'name' => 'MiniMax M2.7',
        'family' => 'minimax',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'pro', 'openrouter'],
      ],
      [
        'model' => 'xiaomi/mimo-v2-pro',
        'name' => 'Xiaomi MiMo V2 Pro',
        'family' => 'mimo',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'pro', 'openrouter'],
      ],
      [
        'model' => 'google/gemini-3.1-pro-preview',
        'name' => 'Google Gemini 3.1 Pro Preview',
        'family' => 'gemini',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'reasoning', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'google/gemini-2.5-flash-lite',
        'name' => 'Google Gemini 2.5 Flash Lite',
        'family' => 'gemini',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'x-ai/grok-4.1-fast',
        'name' => 'xAI Grok 4.1 Fast',
        'family' => 'grok',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'qwen/qwen3.6-plus',
        'name' => 'Qwen 3.6 Plus',
        'family' => 'qwen',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'tools', 'pro', 'openrouter'],
      ],
      [
        'model' => 'openai/gpt-5.2',
        'name' => 'OpenAI GPT-5.2',
        'family' => 'gpt-5',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'responses', 'reasoning', 'verbosity', 'pro', 'openrouter'],
        'tools' => self::TOOLS_FULL,
        'params' => [
          'reasoning' => self::REASONING_GPT5_2,
          'verbosity' => self::VERBOSITY,
        ],
      ],
      [
        'model' => 'openai/gpt-5.2-pro',
        'name' => 'OpenAI GPT-5.2 Pro',
        'family' => 'gpt-5',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'responses', 'reasoning', 'verbosity', 'pro', 'openrouter'],
        'tools' => self::TOOLS_FULL,
        'params' => [
          'reasoning' => self::REASONING_GPT5_2_PRO,
          'verbosity' => self::VERBOSITY,
        ],
      ],
      [
        'model' => 'openai/gpt-4.1',
        'name' => 'OpenAI GPT-4.1',
        'family' => 'gpt-4',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'responses', 'pro', 'openrouter'],
        'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      ],
      [
        'model' => 'openai/gpt-4.1-mini',
        'name' => 'OpenAI GPT-4.1 Mini',
        'family' => 'gpt-4',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'responses', 'pro', 'openrouter'],
        'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      ],
      [
        'model' => 'openai/gpt-4o',
        'name' => 'OpenAI GPT-4o',
        'family' => 'gpt-4o',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'responses', 'pro', 'openrouter'],
        'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      ],
      [
        'model' => 'openai/gpt-4o-mini',
        'name' => 'OpenAI GPT-4o Mini',
        'family' => 'gpt-4o',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'responses', 'pro', 'openrouter'],
        'tools' => ['web_search', 'image_generation', 'code_interpreter'],
      ],
      [
        'model' => 'openrouter/free',
        'name' => 'OpenRouter Free Router',
        'family' => 'router',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'router', 'free', 'openrouter'],
      ],
      [
        'model' => 'openrouter/elephant-alpha',
        'name' => 'Elephant',
        'family' => 'elephant',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'nvidia/nemotron-3-super-120b-a12b:free',
        'name' => 'NVIDIA Nemotron 3 Super (Free)',
        'family' => 'nemotron',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'free', 'openrouter'],
      ],
      [
        'model' => 'arcee-ai/trinity-large-preview:free',
        'name' => 'Arcee AI Trinity Large Preview (Free)',
        'family' => 'trinity',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'free', 'openrouter'],
      ],
      [
        'model' => 'z-ai/glm-4.5-air:free',
        'name' => 'Z.ai GLM 4.5 Air (Free)',
        'family' => 'glm',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'free', 'openrouter'],
      ],
      [
        'model' => 'openai/gpt-oss-120b:free',
        'name' => 'OpenAI gpt-oss-120b (Free)',
        'family' => 'gpt-oss',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'nvidia/nemotron-3-nano-30b-a3b:free',
        'name' => 'NVIDIA Nemotron 3 Nano 30B A3B (Free)',
        'family' => 'nemotron',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'free', 'openrouter'],
      ],
      [
        'model' => 'google/gemma-4-31b-it:free',
        'name' => 'Google Gemma 4 31B (Free)',
        'family' => 'gemma',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'nvidia/nemotron-nano-9b-v2:free',
        'name' => 'NVIDIA Nemotron Nano 9B V2 (Free)',
        'family' => 'nemotron',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'free', 'openrouter'],
      ],
      [
        'model' => 'nvidia/nemotron-nano-12b-v2-vl:free',
        'name' => 'NVIDIA Nemotron Nano 12B 2 VL (Free)',
        'family' => 'nemotron',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'reasoning', 'free', 'openrouter'],
      ],
      [
        'model' => 'minimax/minimax-m2.5:free',
        'name' => 'MiniMax M2.5 (Free)',
        'family' => 'minimax',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'free', 'openrouter'],
      ],
      [
        'model' => 'google/gemma-4-26b-a4b-it:free',
        'name' => 'Google Gemma 4 26B A4B (Free)',
        'family' => 'gemma',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'functions', 'reasoning', 'free', 'openrouter'],
      ],
      [
        'model' => 'openai/gpt-oss-20b:free',
        'name' => 'OpenAI gpt-oss-20b (Free)',
        'family' => 'gpt-oss',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'reasoning', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'qwen/qwen3-coder:free',
        'name' => 'Qwen3 Coder 480B A35B (Free)',
        'family' => 'qwen',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'code', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'meta-llama/llama-3.3-70b-instruct:free',
        'name' => 'Meta Llama 3.3 70B Instruct (Free)',
        'family' => 'llama',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'free', 'openrouter'],
      ],
      [
        'model' => 'qwen/qwen3-next-80b-a3b-instruct:free',
        'name' => 'Qwen3 Next 80B A3B Instruct (Free)',
        'family' => 'qwen',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'functions', 'free', 'openrouter'],
      ],
      [
        'model' => 'openai/text-embedding-3-small',
        'name' => 'OpenAI Text Embedding 3 Small',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'openrouter'],
      ],
      [
        'model' => 'openai/text-embedding-3-large',
        'name' => 'OpenAI Text Embedding 3 Large',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'openrouter'],
      ],
    ];
  }

  /**
   * Return Ollama model metadata definitions.
   */
  public static function ollamaModels(): array {
    $models = [
      // ---------------------------------------------------------------------
      // Chat / completion models.
      // These are common Ollama model names people often pull locally.
      // ---------------------------------------------------------------------
      [
        'model' => 'llama3.2',
        'name' => 'Llama 3.2',
        'family' => 'llama',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'llama3.1',
        'name' => 'Llama 3.1',
        'family' => 'llama',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'qwen3.5',
        'name' => 'Qwen 3.5',
        'family' => 'qwen',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'qwen3',
        'name' => 'Qwen 3',
        'family' => 'qwen',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'mistral',
        'name' => 'Mistral',
        'family' => 'mistral',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'gemma4',
        'name' => 'Gemma 4',
        'family' => 'gemma',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
        'params' => [
          'reasoning' => self::REASONING_O,
        ],
      ],
      [
        'model' => 'gemma3',
        'name' => 'Gemma 3',
        'family' => 'gemma',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],
      [
        'model' => 'phi4',
        'name' => 'Phi 4',
        'family' => 'phi',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'local', 'ollama'],
      ],

      // ---------------------------------------------------------------------
      // Vision-capable models.
      // Ollama's OpenAI-compatible chat endpoint supports vision for models
      // that support it.
      // ---------------------------------------------------------------------
      [
        'model' => 'llama3.2-vision',
        'name' => 'Llama 3.2 Vision',
        'family' => 'llama',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'local', 'ollama'],
      ],
      [
        'model' => 'gemma3',
        'name' => 'Gemma 3',
        'family' => 'gemma',
        'features' => ['completion'],
        'tags' => ['core', 'chat', 'vision', 'local', 'ollama'],
      ],

      // ---------------------------------------------------------------------
      // Embedding models.
      // Ollama supports /v1/embeddings for compatible local models.
      // ---------------------------------------------------------------------
      [
        'model' => 'qwen3-embedding',
        'name' => 'Qwen 3 Embedding',
        'family' => 'qwen',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'local', 'ollama'],
      ],
      [
        'model' => 'nomic-embed-text',
        'name' => 'Nomic Embed Text',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'local', 'ollama'],
      ],
      [
        'model' => 'mxbai-embed-large',
        'name' => 'MXBAI Embed Large',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'local', 'ollama'],
      ],
      [
        'model' => 'bge-m3',
        'name' => 'BGE M3',
        'family' => 'embedding',
        'features' => ['embedding'],
        'tags' => ['core', 'embedding', 'local', 'ollama'],
      ],
    ];

    // De-duplicate by model id because some models may appear in more than one
    // capability section (for example Gemma 3 as chat + vision).
    $map = [];
    foreach ($models as $model) {
      $id = (string) ($model['model'] ?? '');
      if ($id === '') {
        continue;
      }

      if (!isset($map[$id])) {
        $map[$id] = $model;
        continue;
      }

      // Merge tags/features when the same model appears multiple times.
      $existing_features = $map[$id]['features'] ?? [];
      $existing_tags = $map[$id]['tags'] ?? [];

      $map[$id]['features'] = array_values(array_unique(array_merge(
        is_array($existing_features) ? $existing_features : [],
        is_array($model['features'] ?? null) ? $model['features'] : []
      )));

      $map[$id]['tags'] = array_values(array_unique(array_merge(
        is_array($existing_tags) ? $existing_tags : [],
        is_array($model['tags'] ?? null) ? $model['tags'] : []
      )));
    }

    return array_values($map);
  }

  /**
   * Convenience method if you want a single unified list in the future.
   *
   * @param string[] $providers Supported: ['openai','openrouter','replicate','ollama'] (default all)
   * @return array
   */
  public static function allModels(array $providers = []): array {
    $providers = array_map('strtolower', $providers);
    $include_all = empty($providers);

    $all = [];
    if ($include_all || in_array('openai', $providers, true)) {
      $all = array_merge($all, self::openAiModels());
    }
    if ($include_all || in_array('openrouter', $providers, true)) {
      $all = array_merge($all, self::openRouterModels());
    }
    if ($include_all || in_array('replicate', $providers, true)) {
      $all = array_merge($all, self::replicateModels());
    }
    if ($include_all || in_array('ollama', $providers, true)) {
      $all = array_merge($all, self::ollamaModels());
    }

    return $all;
  }

  /**
   * Returns provider models (raw list) by provider key.
   *
   * @param string $provider openai|openrouter|replicate|ollama
   * @return array
   */
  public static function providerModels(string $provider): array {
    $provider = strtolower(trim($provider));
    return match ($provider) {
      'openai' => self::openAiModels(),
      'openrouter' => self::openRouterModels(),
      'replicate' => self::replicateModels(),
      'ollama' => self::ollamaModels(),
      default => [],
    };
  }

  /**
   * Provider models indexed by model id.
   * Uses a static in-request cache to avoid re-building large arrays repeatedly.
   *
   * @param string $provider
   * @return array<string, array>
   */
  public static function providerModelsById(string $provider): array {
    static $cache = [];
    $provider = strtolower(trim($provider));
    if (isset($cache[$provider])) {
      return $cache[$provider];
    }
    $map = [];
    foreach (self::providerModels($provider) as $model_definition) {
      if (!is_array($model_definition)) {
        continue;
      }
      $id = isset($model_definition['model']) ? (string) $model_definition['model'] : '';
      if ($id === '') {
        continue;
      }
      $map[$id] = $model_definition;
    }
    $cache[$provider] = $map;
    return $map;
  }

  /**
   * Get a model definition by provider id.
   *
   * @param string $provider
   * @param string $model_id
   * @return array|null
   */
  public static function getModel(string $provider, string $model_id): ?array {
    $model_id = trim((string) $model_id);
    if ($model_id === '') {
      return null;
    }
    $map = self::providerModelsById($provider);
    return $map[$model_id] ?? null;
  }

  /**
   * Return model ids that declare a given param key, e.g. "reasoning" or "verbosity".
   *
   * @param string $provider
   * @param string $param_key
   * @return string[]
   */
  public static function modelIdsWithParam(string $provider, string $param_key): array {
    $param_key = strtolower(trim($param_key));
    if ($param_key === '') {
      return [];
    }
    $out = [];
    foreach (self::providerModelsById($provider) as $id => $model_definition) {
      $params = (isset($model_definition['params']) && is_array($model_definition['params'])) ? $model_definition['params'] : [];
      if (array_key_exists($param_key, $params) && is_array($params[$param_key]) && !empty($params[$param_key])) {
        $out[] = (string) $id;
      }
    }
    sort($out, SORT_STRING);
    return $out;
  }

  /**
   * Return param values per model id (only for models that declare that param).
   *
   * @param string $provider
   * @param string $param_key
   * @return array<string, string[]>
   */
  public static function paramValuesByModel(string $provider, string $param_key): array {
    $param_key = strtolower(trim($param_key));
    if ($param_key === '') {
      return [];
    }
    $out = [];
    foreach (self::providerModelsById($provider) as $id => $model_definition) {
      $params = (isset($model_definition['params']) && is_array($model_definition['params'])) ? $model_definition['params'] : [];
      $vals = $params[$param_key] ?? null;
      if (!is_array($vals) || empty($vals)) {
        continue;
      }
      $vals = array_values(array_unique(array_values(array_filter(array_map(static function ($value) {
        return is_string($value) ? strtolower(trim($value)) : '';
      }, $vals)))));
      if (!empty($vals)) {
        $out[(string) $id] = $vals;
      }
    }
    ksort($out, SORT_STRING);
    return $out;
  }


  /**
   * Deep-merge with predictable override behavior.
   * - Scalars in $overrides replace base values.
   * - Arrays merge recursively.
   */
  private static function merge(array $base, array $overrides): array {
    return array_replace_recursive($base, $overrides);
  }
}
