import { tauriFetch } from "./http-client"

export interface TransportAdapterConfig {
  baseUrl: string
  apiKey: string
  runtime: string | null
  providerId: string
  providerName: string
}

export interface CompletionRequest {
  model: string
  messages: Array<{ role: string; content: string }>
  stream?: boolean
  maxTokens?: number
  temperature?: number
  topP?: number
  tools?: Array<{ type: string; function: { name: string; description: string; parameters: Record<string, unknown> } }>
  signal?: AbortSignal
  enablePromptCaching?: boolean
}

export interface ToolCallData {
  id: string
  type: string
  function: { name: string; arguments: string }
}

export interface CompletionResponse {
  content: string
  finishReason: string | null
  toolCalls?: ToolCallData[]
  usage?: { promptTokens: number; completionTokens: number; totalTokens: number }
}

export interface ModelsResponse {
  models: Array<{ id: string; name: string }>
}

export interface ProviderCapabilities {
  supportsSystemPrompts: boolean
  supportsToolCalling: boolean
  supportsStreaming: boolean
  supportsVision: boolean
  supportsReasoning: boolean
  supportsJsonMode: boolean
  supportsStructuredOutput: boolean
  supportsCacheControl: boolean
  supportsStreamingTools: boolean
  supportsEmbeddings: boolean
  supportsImageGeneration: boolean
  supportsAudio: boolean
  contextWindow: number
  maxOutputTokens: number
}

export interface TransportAdapter {
  name: string
  getCapabilities(model?: string): ProviderCapabilities
  buildChatUrl(requestModel?: string): string
  buildModelsUrl(): string
  buildHeaders(): Record<string, string>
  buildCompletionBody(req: CompletionRequest): Record<string, unknown>
  parseCompletionResponse(body: string): CompletionResponse
  parseModelsResponse(body: string): ModelsResponse
  normalizeFinishReason(reason: string | null): string | null
}

export function normalizeBaseUrl(raw: string): string {
  return raw.replace(/\/+$/, "")
}

const OPENAI_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: true,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: true,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 128000,
  maxOutputTokens: 16384,
}

export class OpenAITransportAdapter implements TransportAdapter {
  name = "openai-compatible"
  protected config: TransportAdapterConfig

  constructor(config: TransportAdapterConfig) {
    this.config = config
  }

  getCapabilities(model?: string): ProviderCapabilities {
    if (!model) return { ...OPENAI_DEFAULT_CAPS }
    const m = model.toLowerCase()
    const caps = { ...OPENAI_DEFAULT_CAPS }
    if (m.includes("gpt-4o") || m.includes("gpt-4.5")) {
      caps.supportsVision = true
      caps.contextWindow = 128000
      caps.maxOutputTokens = 16384
    } else if (m.includes("gpt-4-turbo")) {
      caps.supportsVision = true
      caps.contextWindow = 128000
      caps.maxOutputTokens = 4096
    } else if (m.includes("gpt-4")) {
      caps.contextWindow = 8192
      caps.maxOutputTokens = 8192
    } else if (m.includes("gpt-3.5")) {
      caps.contextWindow = 16384
      caps.maxOutputTokens = 4096
    } else if (m.includes("o1") || m.includes("o3")) {
      caps.supportsReasoning = true
      caps.supportsVision = true
      caps.contextWindow = 200000
      caps.maxOutputTokens = 100000
    } else if (m.includes("o4")) {
      caps.supportsReasoning = true
      caps.supportsVision = true
      caps.contextWindow = 200000
      caps.maxOutputTokens = 128000
    }
    if (m.includes("vision") || m.includes("vv")) caps.supportsVision = true
    return caps
  }

  buildChatUrl(_requestModel?: string): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    if (base.endsWith("/chat/completions")) return base
    const v1 = base.endsWith("/v1") ? base : `${base}/v1`
    return `${v1}/chat/completions`
  }

  buildModelsUrl(): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    if (base.endsWith("/v1")) return `${base}/models`
    if (base.includes("/v1/")) return `${base}/models`
    return `${base}/v1/models`
  }

  buildHeaders(): Record<string, string> {
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
    }
    if (this.config.apiKey) {
      headers["Authorization"] = `Bearer ${this.config.apiKey}`
    }
    if (this.config.runtime) {
      headers["X-Runtime"] = this.config.runtime
    }
    return headers
  }

  buildCompletionBody(req: CompletionRequest): Record<string, unknown> {
    const body: Record<string, unknown> = {
      model: req.model,
      messages: req.messages,
      max_tokens: req.maxTokens ?? 4096,
    }
    if (req.stream !== undefined) body.stream = req.stream
    if (req.temperature !== undefined) body.temperature = req.temperature
    if (req.topP !== undefined) body.top_p = req.topP
    if (req.tools && req.tools.length > 0) body.tools = req.tools
    return body
  }

  parseCompletionResponse(bodyText: string): CompletionResponse {
    const data = JSON.parse(bodyText)
    const choice = data.choices?.[0]
    const rawToolCalls = choice?.message?.tool_calls
    const toolCalls: ToolCallData[] | undefined = rawToolCalls
      ? rawToolCalls.map((tc: any) => ({
          id: tc.id ?? "",
          type: tc.type ?? "function",
          function: { name: tc.function?.name ?? "", arguments: tc.function?.arguments ?? "" },
        }))
      : undefined
    return {
      content: choice?.message?.content ?? "",
      finishReason: this.normalizeFinishReason(choice?.finish_reason ?? null),
      toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
      usage: data.usage ? {
        promptTokens: data.usage.prompt_tokens ?? 0,
        completionTokens: data.usage.completion_tokens ?? 0,
        totalTokens: data.usage.total_tokens ?? 0,
      } : undefined,
    }
  }

  parseModelsResponse(bodyText: string): ModelsResponse {
    const data = JSON.parse(bodyText)
    const rawModels = data.data || data.models || []
    const models = Array.isArray(rawModels)
      ? rawModels.map((m: any) => ({
          id: String(m.id || m.name || m.model || ""),
          name: String(m.id || m.name || m.model || ""),
        }))
      : []
    return { models }
  }

  normalizeFinishReason(reason: string | null): string | null {
    if (!reason) return null
    const lower = reason.toLowerCase()
    if (lower === "eos" || lower === "end" || lower === "complete") return "stop"
    if (lower === "max_tokens" || lower === "length") return "length"
    if (lower === "tool_calls" || lower === "tool_uses") return "tool_calls"
    if (lower === "content_filter") return "content_filter"
    return lower
  }

}

const NVIDIA_NIM_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: false,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: false,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 128000,
  maxOutputTokens: 4096,
}

export class NvidiaNimAdapter extends OpenAITransportAdapter {
  name = "nvidia-nim"

  getCapabilities(model?: string): ProviderCapabilities {
    const caps = { ...NVIDIA_NIM_CAPS }
    if (!model) return caps
    const m = model.toLowerCase()
    if (m.includes("llama") || m.includes("nemotron")) {
      caps.supportsToolCalling = true
    }
    if (m.includes("vision") || m.includes("vlm")) caps.supportsVision = true
    return caps
  }

  buildModelsUrl(): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    return `${base}/models`
  }

  buildCompletionBody(req: CompletionRequest): Record<string, unknown> {
    const body = super.buildCompletionBody(req)
    if (req.tools && req.tools.length > 0) {
      body.tools = req.tools.map((t) => ({
        type: t.type,
        function: {
          name: t.function.name,
          description: t.function.description,
          parameters: t.function.parameters,
        },
      }))
    }
    return body
  }
}

const OLLAMA_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: false,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: true,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 8192,
  maxOutputTokens: 4096,
}

export class OllamaAdapter extends OpenAITransportAdapter {
  name = "ollama"

  getCapabilities(model?: string): ProviderCapabilities {
    const caps = { ...OLLAMA_CAPS }
    if (!model) return caps
    const m = model.toLowerCase()
    if (m.includes("llava") || m.includes("bakllava") || m.includes("moondream") || m.includes("vision")) {
      caps.supportsVision = true
    }
    if (m.includes("llama3") || m.includes("qwen2") || m.includes("mistral") || m.includes("deepseek") || m.includes("command-r")) {
      caps.supportsToolCalling = true
    }
    if (m.includes("qwen2.5")) {
      caps.contextWindow = 32768
      caps.maxOutputTokens = 8192
    }
    if (m.includes("deepseek-r1")) {
      caps.supportsReasoning = true
      caps.contextWindow = 16384
    }
    return caps
  }

  buildModelsUrl(): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    return `${base.replace(/\/v1$/, "")}/api/tags`
  }

  buildHeaders(): Record<string, string> {
    return { "Content-Type": "application/json" }
  }

  parseModelsResponse(bodyText: string): ModelsResponse {
    const data = JSON.parse(bodyText)
    const raw = data.models || []
    const models = Array.isArray(raw)
      ? raw.map((m: any) => ({
          id: String(m.name || m.model || ""),
          name: String(m.name || m.model || ""),
        }))
      : []
    return { models }
  }
}

const GROQ_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: false,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: false,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 128000,
  maxOutputTokens: 8192,
}

export class GroqAdapter extends OpenAITransportAdapter {
  name = "groq"

  getCapabilities(model?: string): ProviderCapabilities {
    if (!model) return { ...GROQ_DEFAULT_CAPS }
    const caps = { ...GROQ_DEFAULT_CAPS }
    const m = model.toLowerCase()
    if (m.includes("llama")) {
      caps.supportsToolCalling = true
      if (m.includes("3.1") || m.includes("3-1")) caps.contextWindow = 128000
      else caps.contextWindow = 8192
    } else if (m.includes("mixtral")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 32768
    } else if (m.includes("gemma")) {
      caps.contextWindow = 8192
    } else if (m.includes("llava")) {
      caps.supportsVision = true
      caps.contextWindow = 4096
    } else if (m.includes("qwen")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 32768
    } else if (m.includes("deepseek")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 128000
    } else if (m.includes("distil") || m.includes("distill")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 32768
    }
    return caps
  }
}

const DEEPSEEK_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: false,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: false,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 128000,
  maxOutputTokens: 8192,
}

export class DeepSeekAdapter extends OpenAITransportAdapter {
  name = "deepseek"

  getCapabilities(model?: string): ProviderCapabilities {
    if (!model) return { ...DEEPSEEK_DEFAULT_CAPS }
    const caps = { ...DEEPSEEK_DEFAULT_CAPS }
    const m = model.toLowerCase()
    if (m.includes("deepseek-chat")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 128000
      caps.maxOutputTokens = 8192
    } else if (m.includes("deepseek-reasoner") || m.includes("deepseek-r1")) {
      caps.supportsReasoning = true
      caps.supportsToolCalling = false
      caps.contextWindow = 128000
      caps.maxOutputTokens = 8192
    } else if (m.includes("deepseek-coder")) {
      caps.supportsToolCalling = true
      caps.contextWindow = 128000
      caps.maxOutputTokens = 8192
    }
    return caps
  }
}

const OPENROUTER_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: false,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: true,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: false,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 128000,
  maxOutputTokens: 16384,
}

export class OpenRouterAdapter extends OpenAITransportAdapter {
  name = "openrouter"

  getCapabilities(model?: string): ProviderCapabilities {
    if (!model) return { ...OPENROUTER_DEFAULT_CAPS }
    const m = model.toLowerCase()
    const caps = { ...OPENROUTER_DEFAULT_CAPS }
    if (m.includes("vision") || m.includes("vl")) {
      caps.supportsVision = true
    }
    if (m.includes("gpt") || m.includes("o1") || m.includes("o3") || m.includes("o4")) {
      return new OpenAITransportAdapter(this.config).getCapabilities(model)
    }
    if (m.includes("claude")) {
      return new AnthropicTransportAdapter(this.config).getCapabilities(model)
    }
    if (m.includes("gemini")) {
      return new GeminiTransportAdapter(this.config).getCapabilities(model)
    }
    return caps
  }

  buildHeaders(): Record<string, string> {
    return {
      ...super.buildHeaders(),
      "HTTP-Referer": "https://agentic-os.ai",
      "X-Title": this.config.providerName || "AgenticOS",
    }
  }
}

const ANTHROPIC_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: true,
  supportsReasoning: false,
  supportsJsonMode: false,
  supportsStructuredOutput: false,
  supportsCacheControl: true,
  supportsStreamingTools: true,
  supportsEmbeddings: false,
  supportsImageGeneration: false,
  supportsAudio: false,
  contextWindow: 200000,
  maxOutputTokens: 8192,
}

export class AnthropicTransportAdapter implements TransportAdapter {
  name = "anthropic"
  protected config: TransportAdapterConfig

  constructor(config: TransportAdapterConfig) {
    this.config = config
  }

  getCapabilities(model?: string): ProviderCapabilities {
    const caps = { ...ANTHROPIC_DEFAULT_CAPS }
    if (!model) return caps
    const m = model.toLowerCase()
    if (m.includes("claude-3-opus")) {
      caps.maxOutputTokens = 4096
    } else if (m.includes("claude-3-sonnet")) {
      caps.maxOutputTokens = 4096
    } else if (m.includes("claude-3-haiku")) {
      caps.maxOutputTokens = 4096
    } else if (m.includes("claude-3.5-sonnet") || m.includes("claude-3-5-sonnet")) {
      caps.maxOutputTokens = 8192
    } else if (m.includes("claude-3.7-sonnet") || m.includes("claude-3-7-sonnet")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 64000
    } else if (m.includes("claude-3.5-haiku") || m.includes("claude-3-5-haiku")) {
      caps.maxOutputTokens = 8192
    } else if (m.includes("claude-opus-4") || m.includes("opus-4")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 128000
    } else if (m.includes("claude-sonnet-4") || m.includes("sonnet-4")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 128000
    } else if (m.includes("claude-haiku-4") || m.includes("haiku-4")) {
      caps.maxOutputTokens = 64000
    }
    return caps
  }

  buildChatUrl(_requestModel?: string): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    if (base.endsWith("/v1/messages")) return base
    if (base.endsWith("/v1")) return `${base}/messages`
    return `${base}/v1/messages`
  }

  buildModelsUrl(): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    return `${base}/v1/models`
  }

  private latchedHeaders: Record<string, string> | null = null

  buildHeaders(): Record<string, string> {
    if (this.latchedHeaders) return this.latchedHeaders
    this.latchedHeaders = {
      "Content-Type": "application/json",
      "x-api-key": this.config.apiKey,
      "anthropic-version": "2023-06-01",
      "anthropic-beta": ["prompt-caching-2024-07-31", "tools-2024-04-04"].join(","),
    }
    return this.latchedHeaders
  }

  buildCompletionBody(req: CompletionRequest): Record<string, unknown> {
    const systemMessages = req.messages.filter((m) => m.role === "system")
    const nonSystemMessages = req.messages.filter((m) => m.role !== "system")

    const body: Record<string, unknown> = {
      model: req.model,
      messages: nonSystemMessages.map((m) => ({
        role: m.role,
        content: m.content || "",
      })),
      max_tokens: req.maxTokens ?? 8192,
    }
    if (req.stream !== undefined) body.stream = req.stream
    if (req.temperature !== undefined) body.temperature = req.temperature
    if (systemMessages.length > 0) {
      const systemText = systemMessages.map((m) => m.content).join("\n")
      if (req.enablePromptCaching) {
        body.system = [
          { type: "text", text: systemText, cache_control: { type: "ephemeral" } },
        ]
      } else {
        body.system = systemText
      }
    }
    if (req.tools && req.tools.length > 0) {
      body.tools = req.tools.map((t) => ({
        name: t.function.name,
        description: t.function.description,
        input_schema: t.function.parameters,
      }))
    }
    return body
  }

  parseCompletionResponse(bodyText: string): CompletionResponse {
    const data = JSON.parse(bodyText)
    const content = data.content?.map((c: any) => c.text).join("") ?? ""
    const toolUseBlocks = data.content?.filter((c: any) => c.type === "tool_use") ?? []
    const toolCalls: ToolCallData[] | undefined = toolUseBlocks.length > 0
      ? toolUseBlocks.map((block: any) => ({
          id: block.id ?? "",
          type: "function",
          function: { name: block.name ?? "", arguments: JSON.stringify(block.input ?? {}) },
        }))
      : undefined
    return {
      content,
      finishReason: this.normalizeFinishReason(data.stop_reason ?? null),
      toolCalls: toolCalls && toolCalls.length > 0 ? toolCalls : undefined,
      usage: data.usage ? {
        promptTokens: data.usage.input_tokens ?? 0,
        completionTokens: data.usage.output_tokens ?? 0,
        totalTokens: (data.usage.input_tokens ?? 0) + (data.usage.output_tokens ?? 0),
      } : undefined,
    }
  }

  parseModelsResponse(bodyText: string): ModelsResponse {
    const data = JSON.parse(bodyText)
    const raw = data.data || []
    const models = Array.isArray(raw)
      ? raw.map((m: any) => ({ id: String(m.id || ""), name: String(m.id || "") }))
      : []
    return { models }
  }

  normalizeFinishReason(reason: string | null): string | null {
    if (!reason) return null
    if (reason === "end_turn") return "stop"
    if (reason === "max_tokens") return "length"
    if (reason === "tool_use") return "tool_calls"
    return reason
  }

}

const GEMINI_DEFAULT_CAPS: ProviderCapabilities = {
  supportsSystemPrompts: true,
  supportsToolCalling: true,
  supportsStreaming: true,
  supportsVision: true,
  supportsReasoning: false,
  supportsJsonMode: true,
  supportsStructuredOutput: true,
  supportsCacheControl: false,
  supportsStreamingTools: true,
  supportsEmbeddings: true,
  supportsImageGeneration: false,
  supportsAudio: true,
  contextWindow: 1048576,
  maxOutputTokens: 8192,
}

export class GeminiTransportAdapter implements TransportAdapter {
  name = "gemini"
  protected config: TransportAdapterConfig

  constructor(config: TransportAdapterConfig) {
    this.config = config
  }

  getCapabilities(model?: string): ProviderCapabilities {
    const caps = { ...GEMINI_DEFAULT_CAPS }
    if (!model) return caps
    const m = model.toLowerCase()
    if (m.includes("gemini-1.5-pro")) {
      caps.maxOutputTokens = 8192
    } else if (m.includes("gemini-1.5-flash")) {
      caps.maxOutputTokens = 8192
    } else if (m.includes("gemini-2.0-flash") || m.includes("gemini-2.5-flash")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 8192
    } else if (m.includes("gemini-2.5-pro")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 65536
      caps.contextWindow = 1048576
    } else if (m.includes("gemini-2.0-pro")) {
      caps.supportsReasoning = true
      caps.maxOutputTokens = 8192
    }
    return caps
  }

  buildChatUrl(requestModel?: string): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    // Gemini requires model in URL path (e.g., /models/gemini-pro:generateContent)
    const model = requestModel ?? "unknown-model"
    return `${base}/models/${encodeURIComponent(model)}:generateContent`
  }

  buildModelsUrl(): string {
    const base = normalizeBaseUrl(this.config.baseUrl)
    return `${base}/models`
  }

  buildHeaders(): Record<string, string> {
    return {
      "Content-Type": "application/json",
      "x-goog-api-key": this.config.apiKey,
    }
  }

  buildCompletionBody(req: CompletionRequest): Record<string, unknown> {
    const contents = req.messages
      .filter((m) => m.role === "user" || m.role === "assistant" || m.role === "model")
      .map((m) => {
        const parts: Record<string, unknown>[] = []
        if (m.content) {
          parts.push({ text: m.content })
        }
        return {
          role: m.role === "assistant" ? "model" : m.role,
          parts,
        }
      })

    const body: Record<string, unknown> = {
      contents,
      generationConfig: {
        maxOutputTokens: req.maxTokens ?? 8192,
        temperature: req.temperature,
        topP: req.topP,
      },
    }

    if (req.tools && req.tools.length > 0) {
      body.tools = [{
        functionDeclarations: req.tools.map((t) => ({
          name: t.function.name,
          description: t.function.description,
          parameters: t.function.parameters,
        })),
      }]
    }

    return body
  }

  parseCompletionResponse(bodyText: string): CompletionResponse {
    const data = JSON.parse(bodyText)
    const candidates = data.candidates
    const candidate = candidates?.[0]
    const contentParts = candidate?.content?.parts ?? []

    const textParts = contentParts.filter((p: any) => p.text !== undefined)
    const content = textParts.map((p: any) => p.text).join("")

    const functionCallParts = contentParts.filter((p: any) => p.functionCall !== undefined)
    const toolCalls = functionCallParts.length > 0
      ? functionCallParts.map((p: any, i: number) => ({
          id: p.functionCall.name + "_" + i,
          type: "function" as const,
          function: {
            name: p.functionCall.name,
            arguments: typeof p.functionCall.args === "string" ? p.functionCall.args : JSON.stringify(p.functionCall.args ?? {}),
          },
        }))
      : undefined

    const finishReason = candidate?.finishReason ?? null
    return {
      content,
      finishReason: this.normalizeFinishReason(finishReason),
      toolCalls,
      usage: data.usageMetadata ? {
        promptTokens: data.usageMetadata.promptTokenCount ?? 0,
        completionTokens: data.usageMetadata.candidatesTokenCount ?? 0,
        totalTokens: data.usageMetadata.totalTokenCount ?? 0,
      } : undefined,
    }
  }

  parseModelsResponse(bodyText: string): ModelsResponse {
    const data = JSON.parse(bodyText)
    const raw = data.models || []
    const models = Array.isArray(raw)
      ? raw.map((m: any) => ({
          id: String(m.name || m.model || "").replace(/^models\//, ""),
          name: String(m.name || m.model || "").replace(/^models\//, ""),
        }))
      : []
    return { models }
  }

  normalizeFinishReason(reason: string | null): string | null {
    if (!reason) return null
    const upper = reason.toUpperCase()
    if (upper === "STOP") return "stop"
    if (upper === "MAX_TOKENS") return "length"
    if (upper === "SAFETY" || upper === "RECITATION") return "content_filter"
    if (upper === "OTHER") return "stop"
    return reason.toLowerCase()
  }

}

export type AdapterType = "openai" | "anthropic" | "nvidia-nim" | "ollama" | "gemini" | "groq" | "deepseek" | "openrouter"

export function resolveAdapter(config: TransportAdapterConfig): TransportAdapter {
  const url = config.baseUrl.toLowerCase()

  if (url.includes("anthropic.com") || config.runtime === "Anthropic") {
    return new AnthropicTransportAdapter(config)
  }
  if (url.includes("googleapis.com") || url.includes("generativelanguage") || config.runtime === "Google Gemini") {
    return new GeminiTransportAdapter(config)
  }
  if (url.includes("nvidia.com") || config.runtime === "Nvidia NIM") {
    return new NvidiaNimAdapter(config)
  }
  if (url.includes("11434") || url.includes("ollama") || config.runtime === "Ollama") {
    return new OllamaAdapter(config)
  }
  if (url.includes("groq.com") || config.runtime === "Groq") {
    return new GroqAdapter(config)
  }
  if (url.includes("deepseek.com") || config.runtime === "DeepSeek") {
    return new DeepSeekAdapter(config)
  }
  if (url.includes("openrouter.ai") || config.runtime === "OpenRouter") {
    return new OpenRouterAdapter(config)
  }

  return new OpenAITransportAdapter(config)
}
