/**
 * AiHubMix Provider
 *
 * Multi-backend API gateway that routes models by model ID prefix:
 * - claude* -> Anthropic SDK
 * - gemini* -> Google SDK
 * - others -> OpenAI Responses SDK (default)
 *
 * All requests include the APP-Code header.
 */
import { AnthropicMessagesLanguageModel } from '@ai-sdk/anthropic/internal'
import { GoogleGenerativeAILanguageModel } from '@ai-sdk/google/internal'
import { OpenAIChatLanguageModel, OpenAIResponsesLanguageModel, OpenAISpeechModel } from '@ai-sdk/openai/internal'
import { OpenAICompatibleChatLanguageModel, OpenAICompatibleEmbeddingModel } from '@ai-sdk/openai-compatible'
import type { EmbeddingModelV3, ImageModelV3, LanguageModelV3, ProviderV3, RerankingModelV3 } from '@ai-sdk/provider'
import type { FetchFunction } from '@ai-sdk/provider-utils'
import { loadApiKey, withoutTrailingSlash } from '@ai-sdk/provider-utils'
import { OpenAICompatibleRerankingModel } from '@cherrystudio/ai-sdk-provider'

import { createAihubmixImageModel } from './aihubmixImageModel'

export const AIHUBMIX_PROVIDER_NAME = 'aihubmix' as const
const APP_CODE_HEADER = { 'APP-Code': 'MLTG2087' }

// AiHubMix dispatches on raw API model ids. Keep these predicates string-based: the shared
// `@shared/utils/model` helpers resolve the raw id via getRawModelId → parseUniqueModelId, which
// THROWS ('Invalid UniqueModelId format') on a bare API id with no `::`. A fabricated
// `{ id: modelId } as Model` would therefore CRASH on every OpenAI-routed model here — it doesn't
// merely lack metadata. (The chat-completion-only list below has no shared string source, so it
// stays local too.)
const isOpenAILLM = (modelId: string): boolean => {
  const id = modelId.toLowerCase()
  return /\bgpt\b|^o[134]/.test(id) && !id.includes('gpt-4o-image')
}

const isOpenAIChatCompletionOnly = (modelId: string): boolean => {
  const id = modelId.toLowerCase()
  return (
    id.includes('gpt-4o-search-preview') ||
    id.includes('gpt-4o-mini-search-preview') ||
    id.includes('o1-mini') ||
    id.includes('o1-preview')
  )
}

export interface AihubmixProviderSettings {
  apiKey?: string
  baseURL?: string
  headers?: Record<string, string>
  fetch?: FetchFunction
}

export interface AihubmixProvider extends ProviderV3 {
  (modelId: string): LanguageModelV3
  languageModel(modelId: string): LanguageModelV3
  embeddingModel(modelId: string): EmbeddingModelV3
  imageModel(modelId: string): ImageModelV3
  rerankingModel(modelId: string): RerankingModelV3
}

export function createAihubmix(options: AihubmixProviderSettings = {}): AihubmixProvider {
  const { baseURL = 'https://aihubmix.com/v1', fetch: customFetch } = options

  const resolveApiKey = () =>
    loadApiKey({ apiKey: options.apiKey, environmentVariableName: 'AIHUBMIX_API_KEY', description: 'AiHubMix' })

  // Note: Do not hard-code `Content-Type: application/json` here. `postJsonToApi`
  // already defaults it for JSON endpoints, while `postFormDataToApi` (used by
  // `OpenAICompatibleImageModel` for `/images/edits`) relies on fetch to set
  // `multipart/form-data; boundary=...` automatically — forcing JSON here breaks
  // image edits with "invalid character '-' in numeric literal" on the server.
  const authHeaders = (): Record<string, string> => ({
    Authorization: `Bearer ${resolveApiKey()}`,
    ...APP_CODE_HEADER,
    ...options.headers
  })

  const url = ({ path }: { path: string; modelId: string }) => `${withoutTrailingSlash(baseURL)}${path}`

  const createAnthropicModel = (modelId: string) => {
    const headers = authHeaders()
    return new AnthropicMessagesLanguageModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.anthropic`,
      baseURL,
      headers: () => ({ ...headers, 'x-api-key': resolveApiKey() }),
      fetch: customFetch,
      supportedUrls: () => ({ 'image/*': [/^https?:\/\/.*$/] }),
      // AiHubMix may route Claude models to Vertex/Bedrock backends, which reject the
      // `structured-outputs-2025-11-13` beta header added by @ai-sdk/anthropic for
      // claude-opus-4-6 / claude-sonnet-4-6 / claude-*-4-5 / claude-opus-4-1. Falling
      // back to function-tool-based structured outputs keeps tool use (incl. MCP) working
      // across all downstream backends. See issue #14375.
      supportsNativeStructuredOutput: false
    })
  }

  const createGeminiModel = (modelId: string) => {
    const headers = authHeaders()
    return new GoogleGenerativeAILanguageModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.google`,
      baseURL: 'https://aihubmix.com/gemini/v1beta',
      headers: () => ({ ...headers, 'x-goog-api-key': resolveApiKey() }),
      fetch: customFetch,
      generateId: () => `${AIHUBMIX_PROVIDER_NAME}-${Date.now()}`,
      supportedUrls: () => ({})
    })
  }

  const createOpenAICompatibleChatModel = (modelId: string): LanguageModelV3 =>
    new OpenAICompatibleChatLanguageModel(modelId, {
      provider: `openai-compatible.${AIHUBMIX_PROVIDER_NAME}`,
      url,
      headers: authHeaders,
      fetch: customFetch
    })

  const createOpenAIChatModel = (modelId: string): LanguageModelV3 =>
    new OpenAIChatLanguageModel(modelId, {
      provider: `openai-compatible.${AIHUBMIX_PROVIDER_NAME}`,
      url,
      headers: authHeaders,
      fetch: customFetch
    })

  const createResponsesModel = (modelId: string): LanguageModelV3 =>
    new OpenAIResponsesLanguageModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.openai-response`,
      url,
      headers: authHeaders,
      fetch: customFetch,
      fileIdPrefixes: ['file-']
    })

  const createChatModel = (modelId: string): LanguageModelV3 => {
    if (modelId.startsWith('claude')) {
      return createAnthropicModel(modelId)
    }
    if (
      (modelId.startsWith('gemini') || modelId.startsWith('imagen')) &&
      !modelId.endsWith('no-think') &&
      !modelId.endsWith('-search') &&
      !modelId.includes('embedding')
    ) {
      return createGeminiModel(modelId)
    }
    if (isOpenAILLM(modelId)) {
      if (isOpenAIChatCompletionOnly(modelId)) {
        return createOpenAIChatModel(modelId)
      }
      return createResponsesModel(modelId)
    }
    return createOpenAICompatibleChatModel(modelId)
  }

  const provider = (modelId: string) => createChatModel(modelId)
  provider.specificationVersion = 'v3' as const

  provider.languageModel = createChatModel

  provider.embeddingModel = (modelId: string) =>
    new OpenAICompatibleEmbeddingModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.embedding`,
      url,
      headers: authHeaders,
      fetch: customFetch
    })

  provider.imageModel = (modelId: string) =>
    createAihubmixImageModel(modelId, { baseURL, resolveApiKey, headers: authHeaders, fetch: customFetch })

  provider.speechModel = (modelId: string) =>
    new OpenAISpeechModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.speech`,
      url,
      headers: authHeaders,
      fetch: customFetch
    })

  provider.rerankingModel = (modelId: string) =>
    new OpenAICompatibleRerankingModel(modelId, {
      provider: `${AIHUBMIX_PROVIDER_NAME}.rerank`,
      url,
      headers: authHeaders,
      fetch: customFetch
    })

  return provider as AihubmixProvider
}
