import { parse as parseDataUrl } from "@/boxes/dataurl"
import type { ModelMessage } from "ai"
import { mergeDeep, unique } from "remeda"
import type * as Provider from "./provider"

type Modality = NonNullable<import("./models").Model["modalities"]>["input"][number]

function mimeToModality(mime: string): Modality | undefined {
  if (mime.startsWith("image/")) return "image"
  if (mime.startsWith("audio/")) return "audio"
  if (mime.startsWith("video/")) return "video"
  if (mime === "application/pdf") return "pdf"
  return undefined
}

export function sdkKey(npm: string): string | undefined {
  switch (npm) {
    case "@ai-sdk/github-copilot": return "copilot"
    case "@ai-sdk/azure": return "azure"
    case "@ai-sdk/openai": return "openai"
    case "@ai-sdk/amazon-bedrock": return "bedrock"
    case "@ai-sdk/anthropic": case "@ai-sdk/google-vertex/anthropic": return "anthropic"
    case "@ai-sdk/google-vertex": return "vertex"
    case "@ai-sdk/google": return "google"
    case "@ai-sdk/gateway": return "gateway"
    case "@openrouter/ai-sdk-provider": return "openrouter"
    case "@saeeol/gateway": return "openrouter"
  }
  return undefined
}

function normalizeMessages(msgs: ModelMessage[], model: Provider.Model, _options: Record<string, unknown>): ModelMessage[] {
  const filterEmpty = (ms: ModelMessage[]) => ms.map((msg) => {
    if (typeof msg.content === "string") return msg.content === "" ? undefined : msg
    if (!Array.isArray(msg.content)) return msg
    const filtered = msg.content.filter((part) => !(part.type === "text" || part.type === "reasoning") || part.text !== "")
    return filtered.length === 0 ? undefined : { ...msg, content: filtered }
  }).filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")

  if (model.api.npm === "@ai-sdk/anthropic") msgs = filterEmpty(msgs)
  const claude = model.api.id.includes("anthropic") || model.api.id.includes("claude") || model.id.includes("claude")
  if (model.api.npm === "@ai-sdk/amazon-bedrock" && claude) msgs = filterEmpty(msgs)

  if (model.api.id.includes("claude")) {
    const scrub = (id: string) => id.replace(/[^a-zA-Z0-9_-]/g, "_")
    msgs = msgs.map((msg) => {
      if (msg.role === "assistant" && Array.isArray(msg.content)) return { ...msg, content: msg.content.map((part) => (part.type === "tool-call" || part.type === "tool-result") ? { ...part, toolCallId: scrub(part.toolCallId) } : part) }
      if (msg.role === "tool" && Array.isArray(msg.content)) return { ...msg, content: msg.content.map((part) => part.type === "tool-result" ? { ...part, toolCallId: scrub(part.toolCallId) } : part) }
      return msg
    })
  }
  if (["@ai-sdk/anthropic", "@ai-sdk/google-vertex/anthropic"].includes(model.api.npm)) {
    msgs = msgs.flatMap((msg) => {
      if (msg.role !== "assistant" || !Array.isArray(msg.content)) return [msg]
      const parts = msg.content
      const first = parts.findIndex((part) => part.type === "tool-call")
      if (first === -1 || !parts.slice(first).some((part) => part.type !== "tool-call")) return [msg]
      return [{ ...msg, content: parts.filter((part) => part.type !== "tool-call") }, { ...msg, content: parts.filter((part) => part.type === "tool-call") }]
    })
  }
  if (model.providerID === "mistral" || model.api.id.toLowerCase().includes("mistral") || model.api.id.toLocaleLowerCase().includes("devstral")) {
    const scrub = (id: string) => id.replace(/[^a-zA-Z0-9]/g, "").substring(0, 9).padEnd(9, "0")
    const result: ModelMessage[] = []
    for (let i = 0; i < msgs.length; i++) {
      const msg = msgs[i]
      if (msg.role === "assistant" && Array.isArray(msg.content)) msg.content = msg.content.map((part) => (part.type === "tool-call" || part.type === "tool-result") ? { ...part, toolCallId: scrub(part.toolCallId) } : part)
      if (msg.role === "tool" && Array.isArray(msg.content)) msg.content = msg.content.map((part) => part.type === "tool-result" ? { ...part, toolCallId: scrub(part.toolCallId) } : part)
      result.push(msg)
      if (msg.role === "tool" && msgs[i + 1]?.role === "user") result.push({ role: "assistant", content: [{ type: "text", text: "Done." }] })
    }
    return result
  }
  if (model.api.id.toLowerCase().includes("deepseek")) {
    msgs = msgs.map((msg) => {
      if (msg.role !== "assistant") return msg
      if (Array.isArray(msg.content)) {
        if (msg.content.some((part) => part.type === "reasoning")) return msg
        return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] }
      }
      return { ...msg, content: [...(msg.content ? [{ type: "text" as const, text: msg.content }] : []), { type: "reasoning" as const, text: "" }] }
    })
  }
  if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field && model.api.npm !== "@openrouter/ai-sdk-provider") {
    const field = model.capabilities.interleaved.field
    return msgs.map((msg) => {
      if (msg.role === "assistant" && Array.isArray(msg.content)) {
        const reasoningText = msg.content.filter((part: any) => part.type === "reasoning").map((part: any) => part.text).join("")
        return { ...msg, content: msg.content.filter((part: any) => part.type !== "reasoning"), providerOptions: { ...msg.providerOptions, openaiCompatible: { ...msg.providerOptions?.openaiCompatible, [field]: reasoningText } } }
      }
      return msg
    })
  }
  return msgs
}

function applyCaching(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
  const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
  const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
  const providerOptions = { anthropic: { cacheControl: { type: "ephemeral" } }, openrouter: { cacheControl: { type: "ephemeral" } }, bedrock: { cachePoint: { type: "default" } }, openaiCompatible: { cache_control: { type: "ephemeral" } }, copilot: { copilot_cache_control: { type: "ephemeral" } }, alibaba: { cacheControl: { type: "ephemeral" } } }
  for (const msg of unique([...system, ...final])) {
    const useMessageLevel = model.providerID === "anthropic" || model.providerID.includes("bedrock") || model.api.npm === "@ai-sdk/amazon-bedrock"
    if (!useMessageLevel && Array.isArray(msg.content) && msg.content.length > 0) {
      const last = msg.content[msg.content.length - 1]
      if (last && typeof last === "object" && last.type !== "tool-approval-request" && last.type !== "tool-approval-response") { last.providerOptions = mergeDeep(last.providerOptions ?? {}, providerOptions); continue }
    }
    msg.providerOptions = mergeDeep(msg.providerOptions ?? {}, providerOptions)
  }
  return msgs
}

function unsupportedParts(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] {
  return msgs.map((msg) => {
    if (msg.role !== "user" || !Array.isArray(msg.content)) return msg
    const filtered = msg.content.map((part) => {
      if (part.type !== "file" && part.type !== "image") return part
      if (part.type === "image") {
        const imageStr = String(part.image)
        if (imageStr.startsWith("data:")) { const parsed = parseDataUrl(imageStr); if (parsed && (!parsed.base64 || parsed.base64.length === 0)) return { type: "text" as const, text: "ERROR: Image file is empty or corrupted. Please provide a valid image." } }
      }
      const mime = part.type === "image" ? String(part.image).split(";")[0].replace("data:", "") : part.mediaType
      const filename = part.type === "file" ? part.filename : undefined
      const modality = mimeToModality(mime)
      if (!modality || model.capabilities.input[modality]) return part
      return { type: "text" as const, text: `ERROR: Cannot read ${filename ? `"${filename}"` : modality} (this model does not support ${modality} input). Inform the user.` }
    })
    return { ...msg, content: filtered }
  })
}

export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
  msgs = unsupportedParts(msgs, model)
  msgs = normalizeMessages(msgs, model, options)
  if ((model.providerID === "anthropic" || model.providerID === "google-vertex-anthropic" || model.api.id.includes("anthropic") || model.api.id.includes("claude") || model.id.includes("anthropic") || model.id.includes("claude") || model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/alibaba") && model.api.npm !== "@ai-sdk/gateway") msgs = applyCaching(msgs, model)
  const key = sdkKey(model.api.npm)
  if (key && key !== model.providerID) {
    const remap = (opts: Record<string, any> | undefined) => { if (!opts || !(model.providerID in opts)) return opts; const result = { ...opts }; result[key] = result[model.providerID]; delete result[model.providerID]; return result }
    msgs = msgs.map((msg) => {
      if (!Array.isArray(msg.content)) return { ...msg, providerOptions: remap(msg.providerOptions) }
      return { ...msg, providerOptions: remap(msg.providerOptions), content: msg.content.map((part) => (part.type === "tool-approval-request" || part.type === "tool-approval-response") ? { ...part } : { ...part, providerOptions: remap(part.providerOptions) }) } as typeof msg
    })
  }
  return msgs
}
