import { mapValues, mergeDeep, pickBy, omit } from "remeda"
import * as ModelsDev from "./models"
import * as ProviderTransform from "./transform"
import { ModelID, ProviderID } from "./schema"
import { Model, Info } from "./provider-schema"
import { iife } from "@/util/iife"
import { Flag } from "@saeeol/core/flag/flag"
import * as Log from "@saeeol/core/util/log"
import {
  patchModelsDevModel as patchSaeeolModel,
  patchConfigModel as patchSaeeolConfigModel,
} from "@/saeeol/provider/provider"

const log = Log.create({ service: "provider" })

export function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
  const result: Model["cost"] = {
    input: c?.input ?? 0,
    output: c?.output ?? 0,
    cache: {
      read: c?.cache_read ?? 0,
      write: c?.cache_write ?? 0,
    },
  }
  if (c?.context_over_200k) {
    result.experimentalOver200K = {
      cache: {
        read: c.context_over_200k.cache_read ?? 0,
        write: c.context_over_200k.cache_write ?? 0,
      },
      input: c.context_over_200k.input,
      output: c.context_over_200k.output,
    }
  }
  return result
}

export function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
  const base: Model = {
    id: ModelID.make(model.id),
    providerID: ProviderID.make(provider.id),
    name: model.name,
    family: model.family,
    api: {
      id: model.id,
      url: model.provider?.api ?? provider.api ?? "",
      npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
    },
    status: model.status ?? "active",
    headers: {},
    options: {},
    cost: cost(model.cost),
    limit: {
      context: model.limit.context,
      input: model.limit.input,
      output: model.limit.output,
    },
    capabilities: {
      temperature: model.temperature ?? false,
      reasoning: model.reasoning ?? false,
      attachment: model.attachment ?? false,
      toolcall: model.tool_call ?? true,
      input: {
        text: model.modalities?.input?.includes("text") ?? false,
        audio: model.modalities?.input?.includes("audio") ?? false,
        image: model.modalities?.input?.includes("image") ?? false,
        video: model.modalities?.input?.includes("video") ?? false,
        pdf: model.modalities?.input?.includes("pdf") ?? false,
      },
      output: {
        text: model.modalities?.output?.includes("text") ?? false,
        audio: model.modalities?.output?.includes("audio") ?? false,
        image: model.modalities?.output?.includes("image") ?? false,
        video: model.modalities?.output?.includes("video") ?? false,
        pdf: model.modalities?.output?.includes("pdf") ?? false,
      },
      interleaved: model.interleaved ?? false,
    },
    release_date: model.release_date ?? "",
    variants: {},
  }
  Object.assign(base, patchSaeeolModel(provider.id, model)) 

  return {
    ...base,
    variants: mapValues(ProviderTransform.variants(base), (v) => v),
  }
}

export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
  const models: Record<string, Model> = {}
  for (const [key, model] of Object.entries(provider.models)) {
    models[key] = fromModelsDevModel(provider, model)
    for (const [mode, opts] of Object.entries(model.experimental?.modes ?? {})) {
      const id = `${model.id}-${mode}`
      const base = fromModelsDevModel(provider, model)
      models[id] = {
        ...base,
        id: ModelID.make(id),
        name: `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`,
        cost: opts.cost ? mergeDeep(base.cost, cost(opts.cost)) : base.cost,
        options: opts.provider?.body
          ? Object.fromEntries(
              Object.entries(opts.provider.body).map(([k, v]) => [
                k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
                v,
              ]),
            )
          : base.options,
        headers: opts.provider?.headers ?? base.headers,
      }
    }
  }
  return {
    id: ProviderID.make(provider.id),
    source: "custom",
    name: provider.name,
    category: "custom" as const,
    env: [...(provider.env ?? [])],
    options: {},
    models,
  }
}

export function applyConfigModels(
  database: Record<string, Info>,
  configProviders: [string, any][],
  modelsDev: Record<string, ModelsDev.Provider>,
) {
  for (const [providerID, provider] of configProviders) {
    if (!provider) continue 
    const existing = database[providerID]
    const parsed: Info = {
      id: ProviderID.make(providerID),
      name: provider.name ?? existing?.name ?? providerID,
      env: provider.env ?? existing?.env ?? [],
      options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
      source: "config",
      category: existing?.category ?? "cloud",
      models: existing?.models ?? {},
    }

    for (const [modelID, model] of Object.entries(provider.models ?? {}) as [string, any][]) {
      if (!model) continue 
      const existingModel = parsed.models[model.id ?? modelID]
      const apiID = model.id ?? existingModel?.api.id ?? modelID
      const apiNpm =
        model.provider?.npm ??
        provider.npm ??
        existingModel?.api.npm ??
        modelsDev[providerID]?.npm ??
        "@ai-sdk/openai-compatible"
      const name = iife(() => {
        if (model.name) return model.name
        if (model.id && model.id !== modelID) return modelID
        return existingModel?.name ?? modelID
      })
      const parsedModel: Model = {
        id: ModelID.make(modelID),
        api: {
          id: apiID,
          npm: apiNpm,
          url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api ?? "",
        },
        status: model.status ?? existingModel?.status ?? "active",
        name,
        providerID: ProviderID.make(providerID),
        capabilities: {
          temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
          reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
          attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
          toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
          input: {
            text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
            audio: model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
            image: model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
            video: model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
            pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
          },
          output: {
            text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
            audio: model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
            image: model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
            video: model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
            pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
          },
          interleaved:
            model.interleaved ??
            existingModel?.capabilities.interleaved ??
            (!existingModel && apiNpm === "@ai-sdk/openai-compatible" && apiID.includes("deepseek")
              ? { field: "reasoning_content" }
              : false),
        },
        cost: {
          input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
          output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
          cache: {
            read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
            write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
          },
        },
        options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
        limit: {
          context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
          input: model.limit?.input ?? existingModel?.limit?.input,
          output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
        },
        headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
        family: model.family ?? existingModel?.family ?? "",
        release_date: model.release_date ?? existingModel?.release_date ?? "",
        ...patchSaeeolConfigModel(model, existingModel), 
      }
      const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
      parsedModel.variants = mapValues(
        pickBy(merged, (v): v is NonNullable<typeof v> => !!v && !(v as any).disabled), 
        (v) => omit(v, ["disabled"]),
      )
      parsed.models[modelID] = parsedModel
    }
    database[providerID] = parsed
  }
}

export function cleanupProviders(
  providers: Record<ProviderID, Info>,
  cfg: any,
  isProviderAllowed: (id: ProviderID) => boolean,
) {
  for (const [id, provider] of Object.entries(providers)) {
    const providerID = ProviderID.make(id)
    if (!isProviderAllowed(providerID)) {
      delete providers[providerID]
      continue
    }

    const configProvider = cfg.provider?.[providerID]

    for (const [modelID, model] of Object.entries(provider.models)) {
      model.api.id = model.api.id ?? model.id ?? modelID
      if (
        modelID === "gpt-5-chat-latest" ||
        (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
      )
        delete provider.models[modelID]
      if (model.status === "alpha" && !Flag.SAEEOL_ENABLE_EXPERIMENTAL_MODELS) delete provider.models[modelID]
      if (model.status === "deprecated") delete provider.models[modelID]
      if (
        (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
        (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
      )
        delete provider.models[modelID]

      if (!model.variants || Object.keys(model.variants).length === 0) {
        model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
      }

      const configVariants = configProvider?.models?.[modelID]?.variants
      if (configVariants && model.variants) {
        const merged = mergeDeep(model.variants, configVariants)
        model.variants = mapValues(
        pickBy(merged, (v: any): v is NonNullable<typeof v> => !!v && !v.disabled), 
        (v) => omit(v, ["disabled"] as any),
        )
      }
    }

    if (Object.keys(provider.models).length === 0) {
      delete providers[providerID]
      continue
    }

    log.info("found", { providerID })
  }
}
