packages/provider-core/src/server/ModelCatalog.ts

import type {
  ModelCapabilities,
  ProviderDriverKind,
  ServerProviderModel,
} from "@t3tools/contracts";
import * as Context from "effect/Context";
import type * as Effect from "effect/Effect";

import type { ServerProviderDraft } from "./snapshotProbe.ts";

export interface ProviderCatalogModel {
  readonly slug: string;
  readonly name: string;
  readonly shortName?: string;
  readonly subProvider?: string;
  readonly aliases?: ReadonlyArray<string>;
  readonly status: "current" | "legacy";
  readonly badge?: "new";
  readonly capabilities: ModelCapabilities | null;
  readonly adapter: unknown;
  readonly profileAdapter: unknown;
}

export interface ProviderCatalog {
  readonly models: ReadonlyArray<ProviderCatalogModel>;
  readonly defaultChatModel: string | undefined;
}

export class ModelCatalog extends Context.Service<
  ModelCatalog,
  {
    readonly current: (
      driverKind: ProviderDriverKind,
    ) => Effect.Effect<ProviderCatalog | undefined>;
    readonly bundled: (driverKind: ProviderDriverKind) => ProviderCatalog | undefined;
    readonly refreshInBackground: Effect.Effect<void>;
  }
>()("@t3tools/provider-core/server/ModelCatalog") {}

export const catalogServerModel = (
  catalog: ProviderCatalog,
  entry: ProviderCatalogModel,
): ServerProviderModel => ({
  slug: entry.slug,
  name: entry.name,
  ...(entry.shortName ? { shortName: entry.shortName } : {}),
  ...(entry.subProvider ? { subProvider: entry.subProvider } : {}),
  ...(entry.aliases ? { aliases: entry.aliases } : {}),
  ...(entry.badge ? { badge: entry.badge } : {}),
  isCustom: false,
  ...(catalog.defaultChatModel === entry.slug ? { isDefault: true } : {}),
  ...(entry.status === "legacy" ? { isLegacy: true } : {}),
  capabilities: entry.capabilities,
});

export interface CatalogMatching {
  readonly family?: (slug: string) => string;
}

const familyOf = (matching: CatalogMatching | undefined, slug: string) =>
  matching?.family?.(slug) ?? slug;

export function classifyCatalogModels(
  models: ReadonlyArray<ServerProviderModel>,
  catalog: ProviderCatalog | undefined,
  matching?: CatalogMatching,
): ReadonlyArray<ServerProviderModel> {
  return models.map((model) => {
    if (model.isCustom) return model;
    const entry =
      catalog?.models.find((candidate) => candidate.slug === model.slug) ??
      catalog?.models.find((candidate) => candidate.slug === familyOf(matching, model.slug));
    if (entry?.status === "legacy") return model.isLegacy ? model : { ...model, isLegacy: true };
    if (!model.isLegacy) return model;
    const { isLegacy: _isLegacy, ...rest } = model;
    return rest;
  });
}

export function applyCatalogDefault(
  models: ReadonlyArray<ServerProviderModel>,
  catalog: ProviderCatalog | undefined,
  matching?: CatalogMatching,
): ReadonlyArray<ServerProviderModel> {
  const requestedSlug = catalog?.defaultChatModel;
  if (requestedSlug === undefined) return models;
  const slug =
    models.find((model) => model.slug === requestedSlug)?.slug ??
    (matching?.family === undefined
      ? undefined
      : models.find(
          (model) =>
            !model.isCustom && familyOf(matching, model.slug) === familyOf(matching, requestedSlug),
        )?.slug);
  if (slug === undefined) return models;
  const previous = models.find((model) => model.isDefault && model.slug !== slug);
  if (!previous) return models;
  const movedAliases = previous.aliases ?? [];
  return models.map((model) => {
    if (model.slug === previous.slug) {
      const { isDefault: _isDefault, aliases: _aliases, ...rest } = model;
      return rest;
    }
    if (model.slug === slug) {
      const aliases = [...new Set([...(model.aliases ?? []), ...movedAliases])];
      return { ...model, isDefault: true, ...(aliases.length > 0 ? { aliases } : {}) };
    }
    return model;
  });
}

export function applyModelCatalog(
  draft: ServerProviderDraft,
  catalog: ProviderCatalog | undefined,
  matching?: CatalogMatching,
): ServerProviderDraft {
  const { updateRequiredModels: _previous, ...rest } = draft;
  return {
    ...rest,
    models: applyCatalogDefault(
      classifyCatalogModels(draft.models, catalog, matching),
      catalog,
      matching,
    ),
  };
}