/**
 * Model registry — the single source of truth for human-friendly model names.
 *
 * Across the framework a model is referred to three ways:
 *   1. Wire id (Promptfoo provider id): "anthropic:messages:claude-opus-4-6"
 *      — the load-bearing value. Persisted in reports, hashed into eval
 *      fingerprints, emitted to Promptfoo YAML, parsed in many places. NEVER
 *      reformat it.
 *   2. API model id (bare): "claude-opus-4-6" — what the vendor SDK expects.
 *   3. Display name (friendly): "Claude Opus 4.6" — what users should read.
 *
 * This module owns representation (3) and the mapping between the three. It is
 * display-only: nothing here changes a wire value. Authoring friendly names
 * here (rather than re-typing them at each call site or per eval config) keeps
 * one source of truth so labels can't drift.
 *
 * Lookup is lenient by design. It accepts a full wire id, a bare model name,
 * or an agentic-prefixed id ("agentic:naive:claude-opus-4-6") — the model is
 * always resolvable from the trailing colon-segment. Dated revision pins
 * ("...claude-opus-4-5-20251101") fall back to their family entry. An
 * unrecognized model degrades to its bare trailing segment — never the
 * provider-prefixed string.
 *
 * This package is the leaf of the dependency graph (zero runtime deps, no
 * import from @sanity/ailf-core), so the lenient parse below intentionally
 * does NOT reuse core's validated `parseModelId`. The display parse is
 * best-effort and must not throw.
 */
/**
 * Registry of models the framework evaluates. The display names here are the
 * single source of truth — `packages/eval/config/models.ts` derives its
 * `label` fields from this map (via `modelDisplayName`) rather than re-typing
 * them. Add a model here when enrolling it in `config/models.ts`.
 *
 * Role context (e.g. "(grader)") is NOT baked into `displayName` — it belongs
 * to the call site that knows the role, so the same model reads identically
 * wherever it appears.
 */
export const MODEL_REGISTRY = [
    {
        id: "anthropic:messages:claude-opus-4-6",
        provider: "anthropic",
        apiModelId: "claude-opus-4-6",
        displayName: "Claude Opus 4.6",
        shortName: "Opus 4.6",
        vendor: "Anthropic",
    },
    {
        id: "anthropic:messages:claude-sonnet-4-6",
        provider: "anthropic",
        apiModelId: "claude-sonnet-4-6",
        displayName: "Claude Sonnet 4.6",
        shortName: "Sonnet 4.6",
        vendor: "Anthropic",
    },
    {
        id: "anthropic:messages:claude-opus-4-5-20251101",
        provider: "anthropic",
        apiModelId: "claude-opus-4-5-20251101",
        displayName: "Claude Opus 4.5",
        shortName: "Opus 4.5",
        vendor: "Anthropic",
        aliases: ["claude-opus-4-5"],
    },
    {
        id: "openai:chat:gpt-5.2",
        provider: "openai",
        apiModelId: "gpt-5.2",
        displayName: "GPT 5.2",
        shortName: "GPT 5.2",
        vendor: "OpenAI",
    },
    {
        id: "openai:responses:gpt-5.4",
        provider: "openai",
        apiModelId: "gpt-5.4",
        displayName: "GPT 5.4",
        shortName: "GPT 5.4",
        vendor: "OpenAI",
    },
];
/** Placeholder shown when no model id is available. */
const EMPTY_PLACEHOLDER = "—";
/** Index by every string that should resolve to a given descriptor. */
const BY_KEY = new Map();
for (const d of MODEL_REGISTRY) {
    BY_KEY.set(d.id, d);
    BY_KEY.set(d.apiModelId, d);
    for (const alias of d.aliases ?? [])
        BY_KEY.set(alias, d);
}
/** The trailing colon-segment of a wire id is the bare model name. */
function bareName(idOrName) {
    const segs = idOrName.split(":");
    return segs[segs.length - 1] ?? idOrName;
}
/** Map a provider prefix to a known `ModelProvider`. */
function providerOf(idOrName) {
    if (!idOrName.includes(":"))
        return "unknown";
    const prefix = idOrName.split(":")[0];
    if (prefix === "anthropic" || prefix === "openai" || prefix === "google") {
        return prefix;
    }
    // Agentic in-house provider ids ("agentic:naive:<model>") route to the
    // vendor implied by the model name; resolution happens via the registry
    // hit below, so an unmatched agentic id stays "unknown".
    return "unknown";
}
/** Strip a trailing `-YYYYMMDD` revision pin, if present. */
function stripDatePin(name) {
    return name.replace(/-\d{8}$/, "");
}
/**
 * Resolve any reference to a model — full wire id, bare name, agentic-prefixed
 * id, or dated pin — to its descriptor. Unknown models degrade gracefully to a
 * derived descriptor whose names are the bare trailing segment.
 */
export function modelDescriptor(idOrName) {
    if (!idOrName) {
        return {
            id: "",
            provider: "unknown",
            apiModelId: "",
            displayName: EMPTY_PLACEHOLDER,
            shortName: EMPTY_PLACEHOLDER,
            vendor: "",
        };
    }
    const direct = BY_KEY.get(idOrName);
    if (direct)
        return direct;
    const bare = bareName(idOrName);
    const byBare = BY_KEY.get(bare);
    if (byBare)
        return byBare;
    const byFamily = BY_KEY.get(stripDatePin(bare));
    if (byFamily)
        return byFamily;
    return {
        id: idOrName,
        provider: providerOf(idOrName),
        apiModelId: bare,
        displayName: bare,
        shortName: bare,
        vendor: "",
    };
}
/** Full friendly name for a model reference. "—" when absent. */
export function modelDisplayName(idOrName) {
    return modelDescriptor(idOrName).displayName;
}
/** Compact name for a model reference, for tables/badges. "—" when absent. */
export function modelShortName(idOrName) {
    return modelDescriptor(idOrName).shortName;
}
