/** 스키마 정의 + 모델 변환 — provider.ts에서 분리 */

import { mapValues, mergeDeep } from "remeda"
import { Effect, Schema, Types } from "effect"
import { zod } from "@/util/effect-zod"
import { optionalOmitUndefined, withStatics } from "@/util/schema"
import { ModelID, ProviderID } from "./schema"
import * as ModelsDev from "./models"
import * as ProviderTransform from "./transform"
import { patchModelsDevModel as patchSaeeolModel } from "@/saeeol/provider/provider"

const ProviderApiInfo = Schema.Struct({ id: Schema.String, url: Schema.String, npm: Schema.String })
const ProviderModalities = Schema.Struct({ text: Schema.Boolean, audio: Schema.Boolean, image: Schema.Boolean, video: Schema.Boolean, pdf: Schema.Boolean })
const ProviderInterleaved = Schema.Union([Schema.Boolean, Schema.Struct({ field: Schema.Literals(["reasoning_content", "reasoning_details"]) })])
const ProviderCapabilities = Schema.Struct({ temperature: Schema.Boolean, reasoning: Schema.Boolean, attachment: Schema.Boolean, toolcall: Schema.Boolean, input: ProviderModalities, output: ProviderModalities, interleaved: ProviderInterleaved })
const ProviderCacheCost = Schema.Struct({ read: Schema.Finite, write: Schema.Finite })
const ProviderCost = Schema.Struct({ input: Schema.Finite, output: Schema.Finite, cache: ProviderCacheCost, experimentalOver200K: optionalOmitUndefined(Schema.Struct({ input: Schema.Finite, output: Schema.Finite, cache: ProviderCacheCost })) })
const ProviderLimit = Schema.Struct({ context: Schema.Finite, input: optionalOmitUndefined(Schema.Finite), output: Schema.Finite })

export const Model = Schema.Struct({
  id: ModelID, providerID: ProviderID, api: ProviderApiInfo, name: Schema.String, family: optionalOmitUndefined(Schema.String),
  capabilities: ProviderCapabilities, cost: ProviderCost, limit: ProviderLimit, status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
  options: Schema.Record(Schema.String, Schema.Any), headers: Schema.Record(Schema.String, Schema.String), release_date: Schema.String,
  variants: optionalOmitUndefined(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
}).annotate({ identifier: "Model" }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>

export const ProviderCategory = Schema.Literals(["local", "custom", "cloud"])
export type ProviderCategory = typeof ProviderCategory.Type

export const Info = Schema.Struct({
  id: ProviderID, name: Schema.String, category: ProviderCategory, source: Schema.Literals(["env", "config", "custom", "api"]), env: Schema.Array(Schema.String),
  key: optionalOmitUndefined(Schema.String), options: Schema.Record(Schema.String, Schema.Any), models: Schema.Record(Schema.String, Model),
}).annotate({ identifier: "Provider" }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>

const DefaultModelIDs = Schema.Record(Schema.String, Schema.String)
export const ListResult = Schema.Struct({ all: Schema.Array(Info), default: DefaultModelIDs, connected: Schema.Array(Schema.String), failed: Schema.Array(Schema.String) }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type ListResult = Types.DeepMutable<Schema.Schema.Type<typeof ListResult>>
export const ConfigProvidersResult = Schema.Struct({ providers: Schema.Array(Info), default: DefaultModelIDs }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>

export function defaultModelIDs<T extends { models: Record<string, { id: string }> }>(providers: Record<string, T>) {
  return mapValues(providers, (item) => {
    const models = Object.values(item.models ?? {})
    if (models.length === 0) return undefined
    return sortModels(models)[0].id
  })
}

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 }
}

const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
export function sortModels<T extends { id: string }>(models: T[]) {
  const { sortBy } = require("remeda")
  return sortBy(models, [(model: T) => priority.findIndex((filter) => model.id.includes(filter)), "desc"], [(model: T) => (model.id.includes("latest") ? 0 : 1), "asc"], [(model: T) => model.id, "desc"])
}
