// Runtime pricing-catalog refresh. Off by default — enabled with
// `autoRefreshPricing: true` in ~/.thomas/config.json. When on, the daemon
// makes one daily HTTPS GET to models.dev's public price list (the same
// source the bundled catalog is built from), carrying no user data, and
// writes the result to a local overlay the cost resolver reads first.
// Documented in PRIVACY.md. This is the second sanctioned outbound call
// after the npm update check; like that one it's opt-in and hits only a
// public, third-party endpoint — never an openthomas.com / Anthropic host.

import { mkdir, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'
import { PRICING_CATALOG_CACHE } from '../../core/paths.ts'
import type { CatalogProvider } from './catalog.ts'

export const MODELS_DEV_URL = 'https://models.dev/api.json'

// Providers kept in the catalog — agents Thomas users target plus the big
// OpenAI-compatible aggregators. Mirrors scripts/refresh-pricing.ts.
const KEEP_PROVIDERS = new Set([
  'openai',
  'anthropic',
  'google',
  'azure',
  'amazon-bedrock',
  'openrouter',
  'groq',
  'mistral',
  'deepseek',
  'cerebras',
  'fireworks-ai',
  'togetherai',
  'alibaba',
  'alibaba-cn',
  'alibaba-coding-plan',
  'alibaba-coding-plan-cn',
  'moonshot',
  'minimax',
  'baichuan',
  'zhipu',
  'stepfun',
  'ollama',
  'lmstudio',
  'vllm',
  'sglang',
  'perplexity',
  'x-ai',
  'cohere',
  'huggingface',
])

const INPUT_MODALITIES = new Set(['text', 'audio', 'image', 'video', 'pdf'])

type SourceModel = {
  id?: string
  family?: string | null
  cost?: { input?: number; output?: number; cache_read?: number; cache_write?: number }
  modalities?: { input?: string[]; output?: string[] }
}
type SourceProvider = { id?: string; name?: string; models?: Record<string, SourceModel> }

/** Transform models.dev's api.json into the Thomas catalog shape. Shared
 *  with scripts/refresh-pricing.ts so the runtime overlay and the bundled
 *  artifact can't diverge. */
export function transformModelsDev(source: Record<string, SourceProvider>): CatalogProvider[] {
  const out: CatalogProvider[] = []
  for (const [key, provider] of Object.entries(source)) {
    if (!KEEP_PROVIDERS.has(key)) continue
    if (!provider?.models) continue
    const models = []
    for (const [mKey, m] of Object.entries(provider.models)) {
      if (!m?.cost || typeof m.cost.input !== 'number' || typeof m.cost.output !== 'number')
        continue
      const input = Array.isArray(m.modalities?.input)
        ? m.modalities.input.filter((x) => INPUT_MODALITIES.has(x))
        : []
      models.push({
        id: mKey,
        family: m.family ?? null,
        cost: m.cost as CatalogProvider['models'][number]['cost'],
        ...(input.length > 0 ? { input: input as CatalogProvider['models'][number]['input'] } : {}),
      })
    }
    if (models.length === 0) continue
    out.push({ id: key, name: provider.name ?? key, models })
  }
  out.sort((a, b) => a.id.localeCompare(b.id))
  return out
}

/** Fetch the latest catalog and write it to the local overlay. Returns
 *  provider/model counts. Throws on network/parse failure (callers log). */
export async function refreshPricingCatalog(): Promise<{ providers: number; models: number }> {
  const res = await fetch(MODELS_DEV_URL, { signal: AbortSignal.timeout(15_000) })
  if (!res.ok) throw new Error(`HTTP ${res.status}`)
  const source = (await res.json()) as Record<string, SourceProvider>
  const out = transformModelsDev(source)
  await mkdir(dirname(PRICING_CATALOG_CACHE), { recursive: true })
  await writeFile(PRICING_CATALOG_CACHE, JSON.stringify(out), 'utf8')
  const models = out.reduce((n, p) => n + p.models.length, 0)
  return { providers: out.length, models }
}
